单例模式中用volatile和synchronized来知足双重检查锁机制

背景:咱们在实现单例模式的时候每每会忽略掉多线程的状况,就是写的代码在单线程的状况下是没问题的,可是一碰到多个线程的时候,因为代码没写好,就会引起不少问题,并且这些问题都是很隐蔽和很难排查的。html

例子1:没有volatile修饰的uniqueInstancejava

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton(){
    }

    public static Singleton getInstance(){
        if(uniqueInstance == null){ //#1
            synchronized(Singleton.class){ //#2
                if(uniqueInstance == null){ //#3
                    uniqueInstance = new Singleton(); //#4
                    System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
                } else {
                    System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
                }
            }
        }
        return uniqueInstance;
    }
}
 1 public class TestSingleton {
 2     public static void main(final String[] args) throws InterruptedException {
 3         for (int i = 1; i <= 100000; i++) {
 4             final Thread t1 = new Thread(new ThreadSingleton());
 5             t1.setName("thread" + i);
 6             t1.start();
 7         }
 8     }
 9 
10     public static class ThreadSingleton implements Runnable {
11         @Override
12         public void run() {
13             Singleton.getInstance();
14         }
15     }
16 }

这里面的结果有可能会是:(没有真正重现过,太难模拟了)多线程

1 thread2: uniqueInstance is initalized...
2 thread3: uniqueInstance is initalized...
Singleton被实例化两次了,和咱们的单例模式设计指望值不一致:类永远只被实例化一次.

缘由分析:
1. thread2进入#1, 这时子线程的uniqueInstance都是为空的,thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的, thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,因为#1里面拿到的uniqueInstance仍是空(并无及时从thread2里面拿到最新的),因此thread3仍然会执行#4,#5.1
5. 最后在thread2和thread3都实例化了uniqueInstance

例子2:用volatile修饰的uniqueInstanceide

这里就不贴重复的代码了,由于只是加多一个volatile来修饰成员变量:uniqueInstance,spa

可是结果倒是正确的了, 其中一个可能结果:
线程

 

thread2: uniqueInstance is initalized
thread3: uniqueInstance is not null now...

 

缘由分析:设计

volatile(java5):能够保证多线程下的可见性; code

读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程从新拷贝一份,这样就保证子线程的会跟主线程的一致。htm

写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。blog

1. thread2进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2),thread2让出CPU资源给thread3
2. thread3进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2), thread3让出CPO资源给thread2
3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance(因为是volatile修饰的变量,会立刻同步到主线程的变量去)。thread2执行完毕让出CPO资源给thread3
4. thread3接着#1跑下去,跑到#3的时候,会又一次从主线程拷贝一份uniqueInstance!=null回来,因此thread3就直接跑到了#5.2
5. 最后在thread3再也不会重复实例化uniqueInstance了

参考文章:如何在Java中使用双重检查锁实现单例

官方文档说明

深刻理解Java内存模型(一)——基础

双重检查锁定与延迟初始化

相关文章
相关标签/搜索