当将变量声明为volatile时,对这个变量的单个读/写相当于加了锁,但是多个读/写则不是,比如i++,下面看例子:
public class VolatileTest { volatile int i=0; public int get(){ //单个操作,相当于加了synchronized关键字 return i; } public void set(int tmp){ //单个操作,相当于加了synchronized关键字 i=tmp; } public void getAndIncrement(){ //非单个操作,i++,相当于先get(),再加1,再set(); 所以在多线程操作时,如果某个线程get()后,还没有进行set操作,另外一个线程又get()了, 这样导致两个线程get到同一个值。 i++; } public static void main(String[] args){ final VolatileTest volatileTest=new VolatileTest(); for(int i=0;i<=100;i++){ new Thread(){ public void run(){ volatileTest.getAndIncrement(); } }.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(){ public void run(){ System.out.println(volatileTest.get()); } }.start(); } }
下面是上面程序的输出: 可以看到某些数字是重复的。
1
2
3
4
5
6
7
8
9
10
12
12
13
.
.
.
Volatile变量具有以下特性:
1.可见性,任何一个线程对该变量的读操作,总能看到任意线程对这个变量最后的写操作;
2.原子性:对任意单个操作的读/写具有原子性,单对于多操作如i++这种复合操作不具有原子性;
内存可见性:
写内语义:
volatile写的内存语义如下:
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中去。
以上面示例程序VolatileTest为例,假设线程A首先执行writer()方法,随后线程B执行reader()方法,初始时两个线程的本地内存中的flag和a都是初始状态。下图是线程A执行volatile写后,共享变量的状态示意图:
volatile读的内存语义如下:
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来会从主内存中读取共享变量。
下面是线程B读同一个volatile变量后,共享变量的状态示意图: