1、Volatile定义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1、保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2、禁止进行指令重排序。
先看一段代码,假如线程1先执行,线程2后执行:
// 线程1
boolean stop = false;
while(!stop){
doSomething();
}
// 线程2
stop = true;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程。
在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
第一,使用volatile关键字会强制将修改的值立即写入主存
第二,使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
第三,由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
2、Volatile能保证原子性吗?
先看代码:
public class Test {
// volatile修饰
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<500;j++)
test.increase();
};
}.start();
}
try {
Thread.sleep(3000);
} catch(Exception e) {
}
System.out.println(test.inc);
}
}
上述代码的执行结果是一个不确定的数,是一个小于等于5000的数。为什么?因为inc++是不保证原子性的操作,++操作它包括读取变量的原始值、进行加1操作、写入工作内存三步流程。假设线程A读取了inc变量的原始值,虽然是volatile,那就从主内存里直接读咯,读取完成后,这个时候线程A时间片走完,别的线程这个时候把这个值+1了,再回到线程A的时候,再对旧值+1,所以重复了,因此会得到比实际值5000小的值。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面说的volatile变量规则,但是要注意,线程A对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。
怎么改进呢?
第一种,synchronized
public class Test {
// volatile修饰
public volatile int inc = 0;
// 加上synchronized关键字
public synchronized void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<500;j++)
test.increase();
};
}.start();
}
try {
Thread.sleep(3000);
} catch(Exception e) {
}
System.out.println(test.inc);
}
}
第二种,采用锁lock
public class Test {
// volatile修饰
public volatile int inc = 0;
Lock lock = new ReentrantLock();
public void increase() {
lock.lock(); // 加锁
try {
inc++;
} finally{
lock.unlock();
}
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<500;j++)
test.increase();
};
}.start();
}
try {
Thread.sleep(3000);
} catch(Exception e) {
}
System.out.println(test.inc);
}
}
第三种,使用原子操作类Atomic
public class Test {
// 原子操作类,底层使用CAS来进行原子性操作
public AtomicInteger inc = new AtomicInteger();
public void increase() {
inc.getAndIncrement();
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<500;j++)
test.increase();
};
}.start();
}
try {
Thread.sleep(3000);
} catch(Exception e) {
}
System.out.println(test.inc);
}
}
3、Volatile能保证有序性吗?
// x、y为非volatile变量
// flag为volatile变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
4、Volatile应用
// DCL单例模式
public class PersonLazy4 {
// 加上volatile关键字,最值得推荐的方式
private static volatile PersonLazy4 mInstance = null;
private PersonLazy4() {}
/**
* 改进性能,双重判断
*/
public static PersonLazy4 getInstance() {
if (mInstance == null) {
synchronized (PersonLazy4.class) {
if (mInstance == null) {
mInstance = new PersonLazy4();
}
}
}
return mInstance;
}
}
部分内容转载于:http://www.cnblogs.com/dolphin0520/p/3920373.html