先看代码:java
package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static Singleton2 instance; public static Singleton2 getInstance(){ if(instance == null) {//1:读取instance的值 instance = new Singleton2();//2: 实例化instance } return instance; } }
package com.roocon.thread.t5; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MultiThreadMain { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(20); for (int i = 0; i< 20; i++) { threadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+Singleton2.getInstance()); } }); }
threadPool.shutdown();
} }
运行结果:安全
pool-1-thread-4:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-14:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-10:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-8:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-5:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-12:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-1:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-9:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-6:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-2:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-16:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-3:com.roocon.thread.t5.Singleton2@1c208db1 pool-1-thread-17:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-13:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-18:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-7:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-20:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-11:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-15:com.roocon.thread.t5.Singleton2@6519891a pool-1-thread-19:com.roocon.thread.t5.Singleton2@6519891a
发现,有个实例是Singleton2@1c208db1,也就说明,返回的不是同一个实例。这就是所谓的线程安全问题。ide
解释缘由:对于以上代码注释部分,若是此时有两个线程,线程A执行到1处,读取了instance为null,而后cpu就被线程B抢去了,此时,线程A尚未对instance进行实例化。spa
所以,线程B读取instance时仍然为null,因而,它对instance进行实例化了。而后,cpu就被线程A抢去了。此时,线程A因为已经读取了instance的值而且认为它为null,因此,线程
再次对instance进行实例化。因此,线程A和线程B返回的不是同一个实例。code
那么,如何解决呢?对象
1.在方法前面加synchronized修饰。这样确定不会再有线程安全问题。blog
package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static Singleton2 instance; public static synchronized Singleton2 getInstance(){ if(instance == null) {//1 instance = new Singleton2();//2 } return instance; } }
可是,这种解决方式,假若有100个线程同时执行,那么,每次去执行getInstance方法时都要先得到锁再去执行方法体,若是没有锁,就要等待,耗时长,感受像是变成了串行处理。所以,尝试其余更好的处理方式。排序
2. 加同步代码块,减小锁的颗粒大小。咱们发现,只有第一次instance为null的时候,才去建立实例,而判断instance是否为null是读的操做,不可能存在线程安全问题,所以,咱们只须要对建立实例的代码进行同步代码块的处理,也就是所谓的对可能出现线程安全的代码进行同步代码块的处理。内存
package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static Singleton2 instance; public static Singleton2 getInstance(){ if(instance == null) { synchronized (Singleton2.class){ instance = new Singleton2(); } } return instance; } }
可是,这样处理就没有问题了吗?一样的原理,线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null,因而,它开始执行同步代码块中的代码,对instance进行实例化。此时,线程A得到cpu,因为线程A以前已经判断instance值为null,因而开始执行它后面的同步代码块代码。它也会去对instance进行实例化。
这样就致使了仍是会建立两个不同的实例。
那么,如何解决上面的问题。
很简单,在同步代码块中instance实例化以前进行判断,若是instance为null,才对其进行实例化。这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。
再次分析上面的场景:
线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null。因而,它开始执行同步代码块代码,对instance进行了实例化。这是线程A得到cpu执行权,当线程A去执行同步代码块中的代码时,它再去判断instance的值,因为线程B执行完后已经将这个共享资源instance实例化了,因此instance再也不为null,因此,线程A就不会再次实行实例化代码了。
package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static Singleton2 instance; public static synchronized Singleton2 getInstance(){ if(instance == null) { synchronized (Singleton2.class){ if (instance == null){ instance = new Singleton2(); } } } return instance; } }
可是,双重检查加锁并不代码百分百必定没有线程安全问题了。由于,这里会涉及到一个指令重排序问题。instance = new Singleton2()其实能够分为下面的步骤:
1.申请一块内存空间;
2.在这块空间里实例化对象;
3.instance的引用指向这块空间地址;
指令重排序存在的问题是:
对于以上步骤,指令重排序颇有可能不是按上面123步骤依次执行的。好比,先执行1申请一块内存空间,而后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,那么,当它再去执行2步骤,判断instance时,因为instance已经指向了某一地址,它就不会再为null了,所以,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?
加上volatile关键字,由于volatile能够禁止指令重排序。
package com.roocon.thread.t5; public class Singleton2 { private Singleton2(){ } private static volatile Singleton2 instance; public static Singleton2 getInstance(){ if(instance == null) { synchronized (Singleton2.class){ if (instance == null){ instance = new Singleton2(); } } } return instance; } }