java的volatile关键字之非线程安全
volatile的不保证原子性
用volatile修饰的变量,线程在每次使用变量的时候,都会读取主存中变量的最新值。volatile不能用来进行原子性操作。
下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一
执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G cpu:x86 2.4G
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class Counter
{
public static int count
= 0 ;
public static void inc()
{
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep( 1 );
} catch (InterruptedException
e) {
}
count++;
}
public static void main(String[]
args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for ( int i
= 0 ;
i < 1000 ;
i++) {
new Thread( new Runnable()
{
@Override
public void run()
{
Counter.inc();
}
}).start();
}
//这里每次运行的值都有可能不同,可能为1000
System.out.println( "运行结果:Counter.count=" +
Counter.count);
}
}
|
1
|
|
1
|
运行结果:Counter.count=52
|
1
|
实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count= 995 ,可以看出,在多线程的环境下,Counter.count并没有期望结果是 1000
|
1
|
|
1
|
很多人以为,这个是多线程并发问题,只需要在变量count之前加上 volatile 就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class Counter
{
public volatile static int count
= 0 ;
public static void inc()
{
//这里延迟1毫秒,使得结果明显
try {
Thread.sleep( 1 );
} catch (InterruptedException
e) {
}
count++;
}
public static void main(String[]
args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for ( int i
= 0 ;
i < 1000 ;
i++) {
new Thread( new Runnable()
{
@Override
public void run()
{
Counter.inc();
}
}).start();
}
//这里每次运行的值都有可能不同,可能为1000
System.out.println( "运行结果:Counter.count=" +
Counter.count);
}
}
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈, 线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值, 在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图 为什么加上了volatile之后,线程A修改了count值,是该所有其他线程中的count值无效了,还不能得到正确结果呢?? 为什么加了volatile之后还是线程不安全的呢?? 为什么它使用场景要满足那两个条件呢才能保证线程安全呢?? 原因都是虽然线程A修改了count值,是该所有其他线程中的count值无效了,其他线程需要从主存中再次读该count值。但是A修改完后的count可能为同步到主存中。所以说A线程修改前count=1,其他线程再次读取的时候count值可能还是1。所以要满足对变量的写操作不依赖于该变量的当前值等条件
描述这写交互
read and load 从主存复制变量到当前工作内存 其中use and assign 可以多次出现 但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样 对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的 例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值 在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6 线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6(应该是7) 导致两个线程及时用volatile关键字修改之后,还是在并发的时候出现问题。 |
运行结果:Counter.count=412
运行结果还是没有我们期望的1000
volatile保证可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
先看一段代码,假如线程1先执行,线程2后执行:
1
2
3
4
5
6
7
8
|
//线程1
boolean stop
= false ;
while (!stop){
doSomething();
}
//线程2
stop
= true ;
|
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中(写入主存的操作是在update之后和线程退出之前),线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
1)修改volatile变量时会强制将修改后的值刷新的主内存中。
2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
那么线程1读取到的就是最新的正确的值。volatile的使用场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于该变量的当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
下面列举几个Java中使用volatile的几个场景。
1.状态标记量
1
2
3
4
5
6
7
8
9
|
volatile boolean flag
= false ;
while (!flag){
doSomething();
}
public void setFlag()
{
flag
= true ;
}
|
1
2
3
4
5
6
7
8
9
10
|
volatile boolean inited
= false ;
//线程1:
context
= loadContext();
inited
= true ;
//线程2:
while (!inited
){
sleep()
}
doSomethingwithconfig(context);
|
2.double check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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;
}
}
|