volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生机。
volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用volatile关键字的场景。
当一个变量定义为volatile之后,它将具备两种特性:
普通的变量仅仅会保证在该方法的执行过程中所有依赖该赋值结果的地方都能获得正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一直。在一个线程的方法执行过程中无法感知到这点,故Java内存模型描述所谓的“线程内表示为串行的语义”《深入理解Java虚拟机第二版》P369
示例1:验证可见性
public class VolatileTest {
public static int count=0;
public static void main(String[] args) {
for(int i=0;i<100000;i++){
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
}
}
public static synchronized int add(){
count = count + 1;//保证count加一的操作是原子的
return count;
}
public static synchronized int sub(){
//保证count加一的操作是原子的
count = count - 1;
return count;
}
public static synchronized int get(){
return count;
}
}
运行结果:
...
499220
499219
499218
示例2:验证原子性
public class VolatileTest {
public volatile static int count=0;
public static void main(String[] args) {
for(int i=0;i<100000;i++){
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
}
}
public static int add(){
count = count + 1;//保证count加一的操作是原子的
return count;
}
}
运行结果 :
...
499934
499935
499936
示例3:
public class VolatileTest {
public volatile static int count=0;
public static void main(String[] args) {
for(int i=0;i<100000;i++){
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
}
}
public static synchronized int add(){
count = count + 1;//保证count加一的操作是原子的
return count;
}
}
运行结果:
...
499997
499997
499998
499999
500000
首先count使用volatile修饰,保证变量的可见性,在调用add()方法时,给方法加锁,保证原子性,因此结果多次运行也未发现问题,验证volatile的可见性、非原子性。
要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
首先当线程执行这个语句时,会先从主存当中读取count的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。
public class VolatileTest {
public volatile static int count=0;
public volatile static int count1=0;
public volatile static int count2=0;
public volatile static int count3=0;
public volatile static int count4=0;
public volatile static int countTrue=0;
public volatile static int countFalse=0;
public static int x, y;
public volatile static boolean flag = false;
public static void main(String[] args) {
volatileSortTest();
}
public static void volatileSortTest(){
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
public void run() {
x = 1;
y = 2;
flag = true;
x = 3;
y = 4;
count(x);
count(y);
countBool(flag);
}
}).start();
new Thread(new Runnable() {
public void run() {
x = 1;
y = 2;
flag = true;
x = 3;
y = 4;
count(x);
count(y);
countBool(flag);
}
}).start();
}
}
public void volatileWatchTest(){
for(int i=0;i<100000;i++){
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
}
}
public static synchronized int add(){
count = count + 1;//保证count加一的操作是原子的
return count;
}
public static synchronized void count(int count){
//保证count加一的操作是原子的
if (count == 1) {
count1++;
}
if (count == 2) {
count2++;
}
if (count == 3) {
count3++;
}
if (count == 4) {
count4++;
}
System.out.println("1="+count1);
System.out.println("2="+count2);
System.out.println("3="+count3);
System.out.println("4="+count4);
}
public static synchronized void countBool(boolean count){
//保证count加一的操作是原子的
if (count) {
countTrue++;
}
if (!count) {
countFalse++;
}
System.out.println("true="+countTrue);
System.out.println("false="+countFalse);
}
}
运行结果:
1=0
2=1
3=20000
4=19999
true=20000
false=0
由于x,y没有volatile修饰,不能保证有序性,赋值的时候可能存在线程赋值顺序不正确,因此,统计值的结果也可能导致不一致。
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。 并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
public class VolatileTest {
public volatile static int count=0;
public volatile static int count1=0;
public volatile static int count2=0;
public volatile static int count3=0;
public volatile static int count4=0;
public volatile static int countTrue=0;
public volatile static int countFalse=0;
public static int x, y;
public volatile static boolean flag = false;
public static void main(String[] args) {
volatileSortTest();
}
public static void volatileSortTest(){
for (int i = 0; i < 100000; i++) {
new Thread(new Runnable() {
public void run() {
testCount();
}
}).start();
new Thread(new Runnable() {
public void run() {
testCount();
}
}).start();
new Thread(new Runnable() {
public void run() {
testCount();
}
}).start();
}
}
public static void volatileWatchTest(){
for(int i=0;i<100000;i++){
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
//开启10个线程
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 1; j++) {
//每个线程执行加100
add();
}
System.out.print(count+" \n");
}
}).start();
}
}
public static synchronized void testCount(){
x = 1;
y = 2;
flag = true;
x = 3;
y = 4;
count(x);
count(y);
countBool(flag);
}
public static synchronized int add(){
count = count + 1;//保证count加一的操作是原子的
return count;
}
public static synchronized void count(int count){
//保证count加一的操作是原子的
if (count == 1) {
count1++;
}
if (count == 2) {
count2++;
}
if (count == 3) {
count3++;
}
if (count == 4) {
count4++;
}
System.out.println("1="+count1);
System.out.println("2="+count2);
System.out.println("3="+count3);
System.out.println("4="+count4);
}
public static synchronized void countBool(boolean count){
//保证count加一的操作是原子的
if (count) {
countTrue++;
}
if (!count) {
countFalse++;
}
System.out.println("true="+countTrue);
System.out.println("false="+countFalse);
}
}
运行结果为:
第一次:
1=0
2=0
3=20000
4=20000
true=20000
false=0
注意:在使用volatile时,需要保证是在原子操作上处理。因此,testCount()需要加锁。