多线程进阶-JUC

43 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天

1.Volatile

1、保证可见性

 public class JMMDemo {
     // 不加 volatile 程序就会死循环
     // 加 volatile 保证了可见性
     private volatile static int num =0 ;
 ​
     public static void main(String[] args) {
         new Thread(()->{ //线程1 对内存的变化不知道
             while (num == 0){
 ​
             }
         }).start();
 ​
         try {
             TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         num = 1 ;
         System.out.println(num);
     }
 }
 ​

不保证原子性

原子性:不可分割;

线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。

 //volatile 不保证原子性
 public class JMMDemo02 {
     private static int num = 0;
 ​
     public static  void add(){
         num++;
     }
 ​
     public static void main(String[] args) {
         for (int i = 0; i < 20; i++) {
             new Thread(()->{
                 for (int j = 0; j < 1000; j++) {
                    add();
                 }
             }).start();
         }
 ​
         while(Thread.activeCount()>2){
             Thread.yield();
         }
         System.out.println(Thread.currentThread().getName()+"  " +num);
     }
 }
 ​

如果不加lock和synchronized ,怎么样保证原子性?

image-20221011201257888

解决方法:使用原子类,解决原子性问题

image-20221011201441461

 //volatile 不保证原子性
 public class JMMDemo02 {
     private volatile static AtomicInteger num = new AtomicInteger();
 ​
     public static  void add(){
         num.getAndIncrement(); //底层是CAS保证的原子性
     }
 ​
     public static void main(String[] args) {
         for (int i = 0; i < 20; i++) {
             new Thread(()->{
                 for (int j = 0; j < 1000; j++) {
                    add();
                 }
             }).start();
         }
 ​
         while(Thread.activeCount()>2){
             Thread.yield();
         }
         System.out.println(Thread.currentThread().getName()+"  " +num);
     }
 }

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

Unsafe类是一个很特殊的存在;

指令重排

什么是指令重排?

我们写的程序,计算机并不是按照我们自己写的那样去执行的

源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行

处理器在进行指令重排的时候,会考虑数据之间的依赖性!

 int x=1; //1
 int y=2; //2
 x=x+5;   //3
 y=x*x;   //4
 ​
 //我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
 //可不可能是 4123? 不可能的
 ​

可能造成的影响结果:前提:a b x y这四个值 默认都是0

线程A线程B
x=ay=b
b=1a=2

正常的结果: x = 0; y =0;

线程A线程B
x=ay=b
b=1a=2

可能在线程A中会出现,先执行b=1,然后再执行x=a;

在B线程中可能会出现,先执行a=2,然后执行y=b;

那么就有可能结果如下:x=2; y=1.

volatile可以避免指令重排:

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

内存屏障:CPU指令。作用:

1、保证特定的操作的执行顺序;

2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性) image-20221012163911576

总结

  • volatile可以保证可见性;
  • 不能保证原子性
  • 由于内存屏障,可以保证避免指令重排的现象产生

面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式