一、概述
在并发编程中,volatile关键字和synchronized都会被使用来解决并发问题。volatile修饰的变量,保证了对其他线程的可见性,也就是说:它会保证该变量的修改会立即被更新到主存,当有其他线程需要读取时,其他线程每次使用这个变量也必须去主存中读取新值,也就是说一个线程对该变量的修改对其他线程是立马可见的。
参考了:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html的图如下,当使用Volatile关键字时候,线程使用到该变量时候,回直接到主内存(方法区或者堆内存)中加载最新的值,而不是使用线程栈内存中已经加载的值。但是这样只能保证加载、使用的数值是最新的,并不能解决并发问题,因为若两个线程同时加载了这个最新值,并做了修改还是回发生并发问题。并没有为这个值加锁,读写仍然是并发操作的。(对图的一个疑问:在栈内存中对共有变量的持有是,持有一个引用还是持有一个副本?)
通过研究发现:
volatile关键字可以保证属性的可见性,就是说:线程使用这个变量的时候确实会去内存中读取这个变量。
volatile不能像synchornized一样提供原子性的操作序列功能,也就是说即便是volatile关键字修饰变量,也不是线程安全的。
volatile可以再一些初始化或者其他只被改变一次的属性上使用,保证其他线程拿到最新的值。但是在多次修改的值上,最好慎用Volatile来完成线程之间的协同工作。
二、代码演示:
2.1测试代码
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by ryan01.peng on 2017/5/15.
*/
public class LeVolatile {
private static volatile int vInt = 0;
private static boolean publicState = false;
private static volatile boolean volatilePublicState = false;
public static void main(String[] args) throws InterruptedException{
test();
test2();
// test3();
}
/**
* 这个测试方法意在说明,volatile关键字无法提供像synchornized的同步功能。
*/
public static void test() throws InterruptedException{
//
for (int i = 0; i <5000; i++) {
new Thread(){
@Override
public void run() {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LeVolatile.class){ vInt++;}
}
}.start();
}
Thread.sleep(10);
System.out.println("预期输出:5000");
System.out.println(vInt);
}
public static void test1() throws InterruptedException{
//这个测试说明,volatile关键字无法提供像synchornized的同步功能。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new ThreadPoolExecutor.DiscardOldestPolicy());
VolatileProClass v = new VolatileProClass();
for (int i = 0; i < 1500; i++) {
threadPool.execute(new RunTask(i,v));
//new Thread(new RunTask(i, v)).start();
}
Thread.sleep(10);
System.out.println("预期输出的age = 1500");
System.out.println(v);
}
/**
* test2 和 test3 分别对Volatile变量和非Volatile变量进行操作,
* test2可以正常输出和结束,是由于每次使用都在内存中加载volatilePublicState,
* test3因为不会在主存中加载数据,会无法跳出循环,不能退出
*/
public static void test2() throws InterruptedException{
Thread listenThread = new Thread(new Runnable() {
@Override
public void run() {
boolean status = true;
int i = 0;
while (status ){
if (new Date().getTime()%1000 ==0){
if (volatilePublicState != volatilePublicState){
System.out.println(i++ +" 由于Volatile和线程的切换导致了 publicState != publicState");
status = false;
System.exit(0);
}
System.out.println(i++ +" listenThread运行中");
}
}
}
});
Thread.sleep(10);
Thread modifyThread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
volatilePublicState = !volatilePublicState;
}
}
});
listenThread.start();
modifyThread.start();
}
public static void test3() throws InterruptedException{
Thread listenThread = new Thread(new Runnable() {
@Override
public void run() {
boolean status = true;
int i = 0;
while (status ){
if (new Date().getTime()%1000 ==0){
//boolean used = publicState;
if (publicState != publicState){
System.out.println(i++ +" 由于线程的切换导致了 publicState != publicState");
status = false;
return;
}
System.out.println(i++ +" listenThread运行中");
}
}
}
});
Thread.sleep(10);
Thread modifyThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
publicState = !publicState;
}
});
listenThread.start();
modifyThread.start();
}
}
class RunTask implements Runnable {
int i = 0;
VolatileProClass v;
public RunTask(int i, VolatileProClass v) {
this.i = i;
this.v = v;
}
@Override
public void run() {
v.setAge(v.getAge()+1);
v.setFlag(v.isFlag() == false);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
v.setName("name" + i);
}
}
class VolatileProClass {
private volatile String name = "name";
private volatile int age = 0;
private boolean used = false;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isFlag() {
return used;
}
public void setFlag(boolean flag) {
this.used = flag;
}
@Override
public String toString() {
return "VolatileProClass{" +
"name='" + name + '\'' +
", age=" + age +
", used=" + used +
'}';
}
}
3.1测试结果与分析
3.1.1test()的测试结果
预期输出:5000
4980
分析:在线程安全的假设前提下,我们进行了5000次循环,在每个线程中对volatile关键修饰的变量进行了+1操作。但是,在最终结果中我们输出的累加值为4980,并非5000,这就证明了Volatile并不能保证线程安全。
3.1.2 test2()的测试结果
0 listenThread运行中
1 listenThread运行中
2 listenThread运行中
3 由于Volatile和线程的切换导致了 publicState != publicState
分析:这段代码的核心是 publicState != publicState,这是证明Volatile关键字保证可见性的代码。根据Volatile的描述,每次使用都会去主存加载,而不是在线程私有的内存中读取,这样的话publicState != publicState中前一个变量的加载和后一个变量的加载中可能会穿插其他线程对该变量的修改,从而导致不相等成立,才会进入线程结束的条件。
test3()测试结果
0 listenThread运行中
1 listenThread运行中
.....
869 listenThread运行中
870 listenThread运行中
871 listenThread运行中
....
分析:因为变量没有被Volatile关键字修饰,所以listen线程会在线程私有的内存中加载变量,因此 publicState != publicState,不会成立,无法结束循环体。