并发编程之volatile关键字
介绍
使用volatile关键字修饰的变量会对所有线程具有可见性。简单理解就是每当线程要访问该变量时,首先会对该变量值同步到主存,然后再访问该变量值。
如果明白java内存模型就能够更好的理解上面说的意思了。
volatile常被误用当作原子操作,其实volatile最常用的是修饰标识符。
使用volatile关键字必须具备以下两个条件
1、运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
2、变量不需要和其它的状态变量共同参与不变约束
由于volatile变量只能保证可见性,并不能保证原子性操作,在一些场景下我们还是需要通过同步代码块或加锁来保证原子性。
volatile关键字使用
误用场景
Counter Code
public class Counter
{
// 声明可见性变量
private static volatile int count = 0;
// 自增方法
public int incr()
{
return (++count);
}
//setter 和 getter方法
}
CounterThread Code
public class CounterThread implements Runnable
{
private Counter counter;
public CounterThread(Counter counter)
{
this.counter = counter;
}
@Override
public void run()
{
for (int i = 0; i < 1000; i++)
{
counter.incr();
}
}
}
MainApp
public class MainApp
{
public static void main(String[] args)
{
Counter counter = new Counter();
CounterThread counterThread = new CounterThread(counter);
new Thread(counterThread).start();
new Thread(counterThread).start();
new Thread(counterThread).start();
new Thread(counterThread).start();
try
{
Thread.sleep(100);
System.out.println(counter.getCount());
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
//每次计算出的结果都是小于等于4000,不确定的数。这就证明了volatile并不能保证变量操作是原子性的
解决办法
1、使用同步代码块将incr变为同步方法
2、使用锁机制,同步incr内部操作count的代码
3、使用AtomicInteger类型声明变量,然后使用incrementAndGet方法
// 自增方法
public int incr()
{
synchronized (this)//同步代码块
{
return (++count);
}
}
正确的使用场景
1、作状态标识符
public class Resource
{
// 标识符(true表示有资源,false表示没有资源)
private volatile boolean flag = false;
private String name;
}
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;
}
}
总结
volatile关键字修饰的变量只是表明该变量对线程可见,并不能保证变量操作的元仔性。学习java内存模型深入理解。
参考
1、深入理解java虚拟机
2、http://www.ibm.com/developerworks/cn/java/j-jtp06197.html