彻底搞懂volatile关键字
对于volatile这个关键字,相信很多朋友都听说过,甚至使用过,这个关键字虽然字面上理解起来比较简单,但是要用好起来却不是一件容易的事。 计算机中为什么会出现线程不安全的问题volatile既然是与线程安全有关的问题,那我们先来了解一下计算机在处理数据的过程中为什么会出现线程不安全的问题。 t = t + 1; 会先从高速缓存中查看是否有t的值,如果有,则直接拿来使用,如果没有,则会从主存中读取,读取之后会复制一份存放在高速缓存中方便下次使用。之后cup进行对t加1操作,然后把数据写入高速缓存,最后会把高速缓存中的数据刷新到主存中。 这一过程在单线程运行是没有问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的,本次讲解以多核cup为主)。这时就会出现同一个变量在两个高速缓存中的值不一致问题了。 Java中的线程安全问题上面那种线程安全问题,可能对于不同的操作系统会有不同的处理机制,例如Windows操作系统和Linux的操作系统的处理方法可能会不同。
volatile关键字上面扯了一大堆,都没提到volatile关键字的作用,下面开始讲解volatile关键字是如何保证线程安全问题的。 可见性什么是可见性?意思就是说,在多线程环境下,某个共享变量如果被其中一个线程给修改了,其他线程能够立即知道这个共享变量已经被修改了,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取。 volatile保证变量可见性的原理当一个变量被声明为volatile时,在编译成会变指令的时候,会多出下面一行: 0x00bbacde: lock add1 $0x0,(%esp); 这句指令的意思就是在寄存器执行一个加0的空操作。不过这条指令的前面有一个lock(锁)前缀。 缓存一致性协议刚才我在说可见性的时候,说“如果一个共享变量被一个线程修改了之后,当其他线程要读取这个变量的时候,最终会去内存中读取,而不是从自己的工作空间中读取”,实际上是这样的: 有序性实际上,当我们把代码写好之后,虚拟机不一定会按照我们写的代码的顺序来执行。例如对于下面的两句代码: int a = 1; int b = 2; 对于这两句代码,你会发现无论是先执行a = 1还是执行b = 2,都不会对a,b最终的值造成影响。所以虚拟机在编译的时候,是有可能把他们进行重排序的。
public class NoVisibility{ private static boolean ready; private static int number; private static class Reader extends Thread{ public void run(){ while(!ready){ Thread.yield(); } System.out.println(number); } } public static void main(String[] args){ new Reader().start(); number = 42; ready = true; } } 这段代码最终打印的一定是42吗?如果没有重排序的话,打印的确实会是42,但如果number = 42和ready = true被进行了重排序,颠倒了顺序,那么就有可能打印出0了,而不是42。(因为number的初始值会是0).
volatile关键字能够保证代码的有序性,这个也是volatile关键字的作用。
volatile真的能完全保证一个变量的线程安全吗?我们通过上面的讲解,发现volatile关键字还是挺有用的,不但能够保证变量的可见性,还能保证代码的有序性。 原子操作原子操作:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 int a = b + 1;
处理器在处理代码的时候,需要处理以下三个操作:
而这三个操作处理器是不一定就会连续执行的,有可能执行了第一个操作之后,处理器就跑去执行别的操作的。 证明volatile无法保证线程安全的例子由于Java中的运算并非是原子操作,所以导致volatile声明的变量无法保证线程安全。 public class Test{ public static volatile int t = 0; public static void main(String[] args){ Thread[] threads = new Thread[10]; for(int i = 0; i < 10; i++){ //每个线程对t进行1000次加1的操作 threads[i] new Thread(new Runnable(){ @Override public void run(){ for(int j = 0; j < 1000; j++){ t = t + 1; } } }); threads[i].start(); } //等待所有累加线程都结束 while(Thread.activeCount() > 1){ Thread.yield(); } //打印t的值 System.out.println(t); } } 最终的打印结果会是1000 * 10 = 10000吗?答案是否定的。 什么情况下volatile能够保证线程安全刚才虽然说,volatile关键字不一定能够保证线程安全的问题,其实,在大多数情况下volatile还是可以保证变量的线程安全问题的。所以,在满足以下两个条件的情况下,volatile就能保证变量的线程安全问题:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |