volatile是Java提供的一种轻量级的同步机制,它可以保证变量在多线程环境下的可见性(所谓可见性,是指当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的)。
为了更好说明可见性,我们直接写个例子来测试一下:
public class VolatileTest { //public volatile static int num = 0 ; public static int num = 0 ; public static void main(String[] args) throws InterruptedException { final CountDownLatch cdl = new CountDownLatch(1) ; new Thread(new Runnable() { public void run() { long time = System.currentTimeMillis() ; while (num==0){ } System.out.println("结束:"+(System.currentTimeMillis()-time)); cdl.countDown(); } }).start(); System.out.println("开始运行......."); Thread.sleep(1000); num++ ; System.out.println("修改变量num=="+num); cdl.await(); System.out.println("end"); } }
结果:
按正常理解应该1秒后会打印end,但是实际情况是主线程一直在等待。为什么出现这种情况呢,我们需要先了解一下JMM(java内存模型)
以Java内存模型为例,Java内存模型分为主内存(main memory)和工作内存(work memory)。主内存内的变量由所有线程共享,每个线程拥有自己的工作内存,里面的变量包含了线程局部变量。主内存中的变量如果被线程使用到,则线程的工作内存会维护一份主内存变量的副本拷贝。
线程对变量的所有读写操作必须在工作内存中进行,不能直接操作主内存中的变量。不同线程之间也无法直接访问对方的工作内存。线程间变量的传递需通过主内存来完成。线程、主内存、工作内存三者之间的交互关系如下图:
如果线程在自己的执行代码里修改了定义在主线程(主内存)中的变量,修改直接发生在线程的工作内存里,然后在某个时刻(Java程序员无法控制这个时刻,而是由JVM调度的),这个修改从工作内存写回到主内存。
因此我们可知,虽然我们在主线程中修改了变量但是子线程因为读的是自己工作内存的副本所以num还是为0,导致一直死循环。
上面的例子只要我们把num使用volatile修饰即可保存变量在子线程中可见
public volatile static int num = 0 ;
因此我们可以得出一个结论:
volatile具备两种特性,第一就是保证共享变量对所有线程的可见性。将一个共享变量声明为volatile后,会有以下效应:
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去;
2.这个写会操作会导致其他线程中的缓存无效。
*volatile只能保存变量的可见性,并不能保证原子性。因此并不能完全替代synchronized,这个在开发的时候需要特别注意。主要原因是变量的赋值过程主要包含以下3步
1.读取
2.加一
3.赋值
所以,在多线程环境下,有可能线程A将变量读取到本地内存中,此时其他线程可能已经将变量改变,线程A依然对过期的变量进行自加,重新写到主存中,最终导致了结果不合预期
最后发现个疑问:
public class VolatileTest { //public volatile static int num = 0 ; public static int num = 0 ; public static void main(String[] args) throws InterruptedException { final CountDownLatch cdl = new CountDownLatch(1) ; new Thread(new Runnable() { public void run() { long time = System.currentTimeMillis() ; while (num==0){ System.out.println("11111"); } System.out.println("1结束:"+(System.currentTimeMillis()-time)); cdl.countDown(); } }).start(); Thread.sleep(1000); num++ ; cdl.await(); System.out.println("end"); } }
结果:
如果在循环里面System.out.println 一下即使变量num没有被volatile修饰也可以退出循环,目前怀疑子线程中只要调用主线的方法均会重新同步变量。希望各们大神们能有更专业的解答