这几天在复习多线程及高并发的相关知识。volatile关键字一般认为是一种较轻量级的多线程并发解决方案。确实,使用volatile关键字所修饰的变量能够保证变量值一旦更新则所有线程都能够看到最新的值,但这并不意味着使用volatile关键字修饰的变量在做运算操作时能够保证数据的一致性,因为Java的运算操作并非为原子操作。下面我们举个例子:
public class test {
public static volatile int increase = 0;
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[20];
for (Thread thread : threads) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase++;
}
}
});
thread.start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println("输出结果为 ==== " + increase);
}
}
这里我们创建20个线程每个线程对同一个用volatile修饰的变量increase进行累加操作,一般认为此时获取的结果应为20000,但是运行的结果却为:
输出结果为 ==== 71670
输出结果为 ==== 60792
输出结果为 ==== 75454
输出结果为 ==== 72345
输出结果为 ==== 75036
运行5次的结果不但每次都不一样,而且数值没有一次等于20000。这是由volatile关键字的运行原理所致,由volatile关键字所修饰的变量,当某一线程需要使用它的时候会重新获取/刷新对应的变量一遍,然后再进行操作。但是,上面讲过,Java的运行操作并非原子性,因此从获取最新值到执行操作之间该变量仍有可能没其他线程修改,而这时候Java是不会再对该变量进行刷新操作的,因此才会出现上述执行结果。
现在我们再由单例模式解释下,单例模式代码如下:
/**
* 单例模式
* @author aron
*
*/
public class Single implements Serializable{
private static final long serialVersionUID = 1L;
private static volatile Single single;
public static Single getInstance(){
if (single == null) {
synchronized (Single.class) {
if (single == null) {
single = new Single();
}
}
}
return single;
}
private Single() {
}
private Object readResolve(){
return single;
}
}
在这里为什么我们在创建对象前要进行两次对single进行非空判断呢?原因就是volatile修饰的single在第一次判断null的时候首先更新single然后再判断是否为null,而在获取值与判断之间,single对象有可能被其他线程操作,因此判断之后需要对单例类进行加锁,并且再次判断single有没有在第一次获取single和判断null之间有没有被操作,因为这里使用了synchronized上锁,这里的判断不需要担心被其他线程所修改。最后通过查看字节码的方式巩固下:
public class designModel.singleton.Single implements java.io.Serializable {
public static designModel.singleton.Single getInstance();
Code:
0: getstatic #1 <========== 这里更新single变量
<========== 在判断之前有可能其他线程对single进行操作
3: ifnonnull 37 <========== 这里进行第一次非null判断
6: ldc #2
8: dup
9: astore_0
10: monitorenter <========== 进入同步(synchronzied)模块
11: getstatic #1 <========== 这里更新single变量
<========== 因为这里对类进行加锁,在第二次非null判断之间不需要担心single被其他线程操作
14: ifnonnull 27 <========== 这里进行第二次非null判断
17: new #2 <========== 创建single实例
20: dup
21: invokespecial #3
24: putstatic #1
27: aload_0
28: monitorexit
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #1
40: areturn
Exception table:
from to target type
11 29 32 any
32 35 32 any
}