单例模式的应用场景就再也不叙述了,相信网上一找一大把。主要说一说单例模式的线程安全问题。单例模式主要分为两种实现:饿汉模式和懒汉模式。安全
饿汉模式是指,项目一旦被启动,该类就会被加载完成,后续不在须要建立,整个项目的运行过程当中就只有一个实例。实现以下所示:多线程
public class Singleton { private Singleton () { System.out.println("我被建立了!"); } /** * 饿汉模式,类加载时就会建立实例 */ private static Singleton instance = new Singleton(); @PostConstruct public void init() { System.out.println("啥时候"); } public static Singleton getInstance() { return instance; } }
该实例在类被加载时就已经建立,后续使用的都是同一个实例,不会再次建立,因此是线程安全的。也就是说单例模式的饿汉模式自然就不存在线程安全问题。并发
懒汉模式与饿汉模式的最大区别就是,懒汉模式是在该类须要被使用的时候才会建立实例对象,而不是系统启动时加载类就建立。实现以下所示:单元测试
public class LazySingleton { private LazySingleton() { //看看是否会屡次建立 System.out.println("我被建立了!"); } /** * 懒汉模式,使用时才会建立实例 */ private static LazySingleton instance = null; //线程安全 public static LazySingleton getInstance1() { try { //经过休眠,模拟并发状况 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (instance == null) { synchronized (LazySingleton.class) { if (instance == null) { instance = new LazySingleton(); return instance; } } } return instance; } //线程不安全 public static LazySingleton getInstance2() { try { //经过休眠,模拟并发状况 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (instance == null) { instance = new LazySingleton(); return instance; } return instance; } }
如代码中所示,getInstance2 就是正常状况下的懒汉模式实现,咱们经过休眠来模拟并发状况,在单元测试时,发现该类被实例化了2次,说明这种实如今单线程是没问题的,可是在多线程时就会发生线程安全问题。测试
@Test public void singleTest() { for (int i =0; i<100; i++) { new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); new Thread(LazySingleton::getInstance2).start(); } }
运行结果以下图所示:
此时,为确保懒汉模式状况下避免发生线程安全问题,咱们就须要使用到同步锁,如代码中getInstance1 所示,咱们为实例化的操做进行加锁,同时进行double check 操做,以免屡次实例化。固然避免线程安全问题的一种解决方法,还有其余方法,就不在此赘述。在加上同步锁以后,咱们再进行单元测试时,发现果真只建立了一次实例,完美地解决了线程安全问题。spa
@Test public void singleTest() { for (int i =0; i<100; i++) { new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); new Thread(LazySingleton::getInstance1).start(); } }
运行结果以下图所示:线程
饿汉模式:由于在类加载是就已经建立了实例,后续不须要在建立实例,自然地避开了线程安全问题。
懒汉模式:由于是在使用时才进行实例化操做,因此存在着线程安全问题,此时须要经过加同步锁来确保多线程安全,避免屡次实例化。code