下面是一共通过volatile实现原子性的例子:
通过建立100个线程,计算number这个变量最后的结果。
package com.Sychronized; public class VolatileDemo { private volatile int number=0; public int getNumber() { return this.number; } public void increase() { this.number++; } public static void main(String[] args) { // TODO Auto-generated method stub final VolatileDemo volDemo=new VolatileDemo(); for(int i=0;i<100;i++) { new Thread(new Runnable() { @Override public void run() { volDemo.increase(); } }).start(); } //如果还有子线程在运行,主线程就让出CPU资源 //直到所有的子线程都运行完了,主线程再继续往下执行 while(Thread.activeCount()>1) { Thread.yield(); } System.out.println("number:"+volDemo.getNumber()); } }
运行结果:
发现有几种结果:
造成这个结果的原因就是,volatile关键字具有可见性,number++实际上有三步操作,但是不具备原子性。
程序分析:
number++包含三步操作:1,读取number的值,2,number的值加,3,写入number的值给number变量。
假如当前nubmer=5
1,线程A读取number的值
2,线程B读取number的值
3,线程B执行加1操作
4,线程B写入最新的number的值。
此时:主内存:number=6,线程B工作内存:number=6,线程A工作内存:number=5。
5,线程A执行加1操作
6,线程A写入最新的number值。
两次number++操作,只增加了1。
解决方案:
1,使用synchronized关键字
2,使用ReentrantLock(java.until.concurrent.locks包下)
3,使用AtomicInterger(java.util.concurrent.atomic包下)
可以看这个文章:
Java并发编程:volatile关键字解析:http://www.importnew.com/18126.html
第一种:
private int number=0; public int getNumber() { return this.number; } public synchronized void increase() { this.number++; }
或者:
private int number=0; public int getNumber() { return this.number; } public void increase() { synchronized(this) { this.number++; } }
第二种:
对执行加锁处理的地方使用 try finally。。操作,在finally里面,执行lock.unlock释放锁。
private int number=0; private Lock lock = new ReentrantLock(); public int getNumber() { return this.number; } public void increase() { lock.lock(); try { this.number++; }finally { lock.unlock(); } }
volatile使用场合
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
- .不满足:number++,count=count*5等
- .满足:boolean变量,记录温度变化的变量等
2)该变量没有包含在具有其他变量的不变式中
- 不满足:不变式:low<up
.实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
volatile使用场景
(同样摘自文章Java并发编程:volatile关键字解析:http://www.importnew.com/18126.html)
1.状态标记量
volatile boolean flag = false; while(!flag){ doSomething(); } public void setFlag() { flag = true; }
volatile boolean inited = false; //线程1: context = loadContext(); inited = true; //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
2.double check
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }
synchronized和volatile比较
- volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;
- 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于解锁。
- synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。