Volatile理解

volatile是JVM提供的轻量级的同步机制;

  • 保证可见性;
  • 不保证原子性;
  • 禁止指令重排序;

JMM三大特性

  • 可见性 (及时通知机制)
  • 原子性
  • 有序性

资源类:

  1. class MyData{
  2. volatile int number = 0;
  3. public void addTo60(){
  4. this.number = 60;
  5. }
  6. //此时number前面加了volatile关键字
  7. public void addplusplus(){
  8. number++;
  9. }
  10. AtomicInteger atomicInteger = new AtomicInteger();
  11. public void addMyAtomic(){
  12. atomicInteger.getAndIncrement();
  13. }
  14. }

主线程:

/**
 * @Author: Stefan.
 * @Date: 2022/06/11/22:11
 * @Description:  验证volatile的可见性
 *
 *  1.1 假如 int number = 0; number 变量之前根本没有添加volatile关键字修饰,没有可见性
 *  1.2 添加了volatile,可以解决可见性问题
 *
 *
 *  2.1 原子性指的是什么意思?  不可分割 完整性 
        也即某个线程正在做某个具体业务的时候中间不可以被加塞 或者 被分割
 *      需要整体完整 ,要么整体成功 要么整体失败
 *
 *  2.2 volatile是否可以保证原子性? 不保证原子性
 *  2.3 why?
 *  2.4 怎么解决不保证原子性?
 *       加synchronized
 *       使用juc下AtomicInteger
 */
public class VolatileDemo {

  public static void main(String[] args) {

    MyData myData = new MyData();

    for (int i = 0; i < 20; i++) {

      new Thread( () -> {

        for (int j = 0; j < 1000; j++) {
            myData.addplusplus();
            myData.addMyAtomic();
        }

      },String.valueOf(i)).start();
    }

    //需要等待上面20个线程都全部计算完成后,在用mian线程取得最终的结果值是多少?
    while(Thread.activeCount() > 2){
      Thread.yield();   //礼让线程  将控制权交出去
    }

    System.out.println(Thread.currentThread().getName()+ "\t finally number value: " + myData.number);
    System.out.println(Thread.currentThread().getName()+ "\t finally number value: " + myData.atomicInteger);

  }

  /**
   * volatile 可以保证可见性。及时通知其他线程,主物理内存的值已经被修改了
   */
  public static void seeOkByVolatile(){

    MyData myData = new MyData();

    new Thread( () -> {

      System.out.println(Thread.currentThread().getName() + "\t  come in");
      try {
        TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      myData.addTo60();
      System.out.println(Thread.currentThread().getName() + "\t  update number value: " + myData.number);

    },"AAA").start();

    //第二个线程就是main线程
    while(myData.number == 0){
      //main线程就一直在这里等待循环,知道number的值不在等于0
    }

    System.out.println(Thread.currentThread().getName() + "\t  mission is over, main get number value: " + myData.number);
  }

}

可见性
image.png

有序性

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为以下3种
image-20220612103858924.png

单线程环境下确保程序执行结果和代码顺序执行的结果一致;

处理器在进行重排序时必须要考虑指令之间的数据依赖性;

多线程环境下线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测;

CAS

比较并交换

Unsafe类 rt.ja/sun/misc包中
image-20220612145856650.png

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都是直接调用操作系统底层资源执行相应任务;
image-20220612150633254.png

变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的;
image-20220612151233412.png

/**
 * @Author: Stefan.
 * @Date: 2022/06/12/14:24
 * @Description:
 *
 *   cas    比较并交换
 *
 */
public class CASDemo {

  public static void main(String[] args) {

    AtomicInteger atomicInteger = new AtomicInteger(5);  //主物理内存的值 5

    //main do something...


    System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data: " + atomicInteger.get());

    System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get());

  }

}

总结:
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,
否则继续比较直到主内存和工作内存中的值一致为止;

ABA问题

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差里会存在数据的变化;

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出了A,并且线程two进行了操作将值变为B,然后线程two又将V位置的数据变为A,这时候线程one进行CAS操作发现内存中仍然是A,就会操作写回数据并通知其他线程;

尽管线程one的CAS操作成功,但是不代表这个过程是没有问题的;

死锁排查

jps -l :命令定位进程号
jstack 进程号: 找到死锁查看问题
image.png