内存可见性
由于 Java
内存模型(JMM
)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。
这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存。
如下图所示:
所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。
显然这肯定是会出问题的,因此 volatile
的作用出现了:
当一个变量被
volatile
修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
volatile
修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中。
内存可见性的应用
当我们需要在两个线程间依据主内存通信时,通信的那个变量就必须的用 volatile
来修饰:
public class Volatile implements Runnable{ private static volatile boolean flag = true ; @Override public void run() { while (flag){ System.out.println(Thread.currentThread().getName() + "正在运行。。。"); } System.out.println(Thread.currentThread().getName() +"执行完毕"); } public static void main(String[] args) throws InterruptedException { Volatile aVolatile = new Volatile(); new Thread(aVolatile,"thread A").start(); System.out.println("main 线程正在运行") ; TimeUnit.MILLISECONDS.sleep(100) ; aVolatile.stopThread(); } private void stopThread(){ flag = false ; } }
主线程在修改了标志位使得线程 A 立即停止,如果没有用 volatile
修饰,就有可能出现延迟。
但这里有个误区,这样的使用方式容易给人的感觉是:
对
volatile
修饰的变量进行并发操作是线程安全的。
这里要重点强调,volatile
并不能保证线程安全性!
如下程序:
public class VolatileInc implements Runnable{ private static volatile int count = 0 ; //使用 volatile 修饰基本数据内存不能保证原子性 //private static AtomicInteger count = new AtomicInteger() ; @Override public void run() { for (int i=0;i<10000 ;i++){ count ++ ; //count.incrementAndGet() ; } } public static void main(String[] args) throws InterruptedException { VolatileInc volatileInc = new VolatileInc() ; Thread t1 = new Thread(volatileInc,"t1") ; Thread t2 = new Thread(volatileInc,"t2") ; t1.start(); //t1.join(); t2.start(); //t2.join(); for (int i=0;i<10000 ;i++){ count ++ ; //count.incrementAndGet(); } System.out.println("最终Count="+count); } }
当我们三个线程(t1,t2,main)同时对一个 int
进行累加时会发现最终的值都会小于 30000。
这是因为虽然
volatile
保证了内存可见性,每个线程拿到的值都是最新值,但count ++
这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。
- 所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。
- 也可以使用
synchronize
或者是锁的方式来保证原子性。
- 还可以用
Atomic
包中AtomicInteger
来替换int
,它利用了CAS
算法来保证了原子性。