java单例模式是一种常见的设计模式。
单例模式有以下特点:
1、单例类只能有一个实例;
2、单例类必须自己创建自己的唯一实例;
3、单例类必须给所有其他对象提供这一实例;
优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。
常见的单例模式实现方式有五种:饿汉式、懒汉式、双重检测锁、静态内部类和枚举单例。
public class SingletonDemoInHunger { // 私有实例,类初始化就加载 private static SingletonDemoInHunger instance = new SingletonDemoInHunger(); // 私有构造方法 private SingletonDemoInHunger() {} // 公共的、静态的获取实例方法 public static SingletonDemoInHunger getInstance() { return instance; } }
饿汉式:
classloader
机制避免了多线程的同步问题,线程安全;public class SingletonDemoInLazy { // 私有实例,初始化的时候不加载(延迟加载) private static SingletonDemoInLazy instance; // 私有构造 private SingletonDemoInLazy() {} // 公共获取实例方法(线程不安全) public static SingletonDemoInLazy getInstance() { if(instance == null ) { // 使用的时候加载 instance = new SingletonDemoInLazy(); } return instance; } }
上面这种写法,是线程不安全的,但是可以做到延迟加载。
下面是线程安全的懒汉模式:
public class SingletonDemoInLazy { // 私有实例,初始化的时候不加载(延迟加载) private static SingletonDemoInLazy instance; // 私有构造 private SingletonDemoInLazy() {} // 公共获取实例方法(线程安全,调用效率低) public synchronized static SingletonDemoInLazy getInstance() { if(instance == null ) { instance = new SingletonDemoInLazy(); } return instance; } }
上面代码中,通过关键字synchronized
声明公共的获取实例的方法getInstance()
,可以确保线程安全,能做到延迟加载,但是效率不高。
public class SingletonDemoInDoubleCheckLock { // 私有实例,volatile关键字,禁止指令重排。 private volatile static SingletonDemoInDoubleCheckLock instance; // 私有构造 private SingletonDemoInDoubleCheckLock() {} // 公共获取实例方法(线程安全) public static SingletonDemoInDoubleCheckLock getInstance() { if(instance == null ) { // 一重检查 synchronized (SingletonDemoInDoubleCheckLock.class) { if(instance == null) { // 二重检查 instance = new SingletonDemoInDoubleCheckLock(); } } } return instance; } }
在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。
加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例。
双重检查锁:
双重判断,延迟加载;
线程安全;
JDK 版本要求1.5起。
3.4 静态内部类
public class SingletonDemoInStaticInnerClass { // 静态内部类 private static class InnerClass{ // 初始化实例 private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass(); } // 私有构造 private SingletonDemoInStaticInnerClass() {} // 公关获取实例方法(线程安全,延迟加载) public static SingletonDemoInStaticInnerClass getInstance() { return InnerClass.INSTANCE; } }
静态内部类:
classloader
机制来保证初始化 instance
时只有一个线程,线程安全;getInstance
方法时,才会显式装载静态内部类,从而实例化instance
,延迟加载。public enum SingletonEnum { // 枚举元素本身就是单例 INSTANCE; // 其他要执行的方法 public void sayHello() { System.out.println("你好"); } ...... }
枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的。
如何选用哪种方式实现单例模式?
一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。