手写一个单例模式是 Java 面试中常见的问题,很多时候我们更偏向于简单的写一个饿汉或饱汉模式,深入研究的甚少,这里列举三种实现方式,并对各自的优缺进行分析。
1. 饿汉式
public class Singleton { private Singleton(){} private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
这种方式要求类在加载时就要实例化,所以程序开始时就可能产生许多暂时没有不会用到的实例,减缓了程序初始化过程。但它写法简便,线程安全,是完成相关题目时的首选,同时这也是部分 IDE 工具在自动创建单例的时候默认采用的写法。
2. 饱汉式
public class Singleton { private Singleton(){} private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
首次获取该类实例时才进行实例化,符合程序设计要求,同时结构还算清晰,一般也能准确编写。但这种实现方式本身线程不安全,需要用 synchronized 关键字做方法同步。
3.双重锁式
publc class Singleton { private Singleton(){} private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
其实这种方式可以看作是饱汉模式的一种改进,主要考虑到同步是一种高开销的操作,应该尽量减少同步的内容,所以这里在获取实例的静态方法里面做了代码块同步,不再像之前,每次启用 getInstance 方法获取实例时,不管实例是否已经存在都会对后续操作进行同步处理。这里先对实例是否存在进行判断,当确实例不存在确实需要进行实例化操作时再做后续操作,而在入同步代码块前一刻始终有可能另一个线程已经对其完成了实例化,所以还要再做第二次判断,volatile 能保证各线程对 instance 进行操作时的可见性,即保证每次进行判断时 instance 的值都是最新的。