volatile特性
volatile保证可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的
2)禁止进行指令重排序
volatile不能确保原子性
在访问volatile变量时,不会执行加锁操作,因此也就不会执行线程阻塞。所以volatile变量是一种比sychhronized关键字更轻量级的同步机制
关于这一点,可看个例子:
public class VolatileDemo { private volatile int number = 0; public int getNumber() { return this.number; } public void add() { this.number++; } public static void main(String[] args) { final VolatileDemo demo = new VolatileDemo(); for (int i = 0; i < 500; i++) { new Thread(new Runnable() { @Override public void run() { demo.add(); } }).start(); } // 如果还有子线程在运行,主线程就让出CPU资源 // 直到所有的子线程都运行完了,主线程再继续往下执行 while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println("number:" + demo.getNumber()); } }
大家想一下这段程序的输出结果是多少?也许有些朋友认为是500,但是事实上运行它会发现偶尔会出现小于500的值。如果在number++前,加一个Thread.sleep(),效果更明显
接下来,我们分析一下为啥会出现这种情况:
假如某个时刻变量number=5
1. 线程A读取number的值,然后阻塞
2. 线程B读取number的值
3. 线程B执行加1操作
4. 线程B写入最新的number的值(6)到主内存中。此时线程A工作内存中number还是5
5. 线程A执行加1操作
6. 线程A写入最新的number值(6)到主内存中
两个线程分别进行了一次自增操作后,number只增加了1。
根源就在自增操作不是原子性操作,而volatile也无法保证对变量的任何操作都是原子性
解决方案:可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger。
具体例子:
public void add2() { // 加上synchronized后,就可以保证原子性 synchronized (this) { this.number++; } } public void add3() { // 加上lock后,保证原子性 lock.lock(); try { this.number++; } finally { lock.unlock(); } } //AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减 private AtomicInteger count = new AtomicInteger(0); public void add4() { count.getAndIncrement(); }
volatile的适用场景:
1、对变量的写入操作不依赖其当前值(如 number++ 、count = count * 5等)
2、该变量没有包含在具有其它变量的不变式中
3、volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger
参考资料:http://www.importnew.com/24082.html