单例模式多是最经常使用到的设计模式了,可是想要正确的使用单例模式却并不简单。
咱们先从最简单最经常使用的方式开始:html
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
私有的静态内部引用实例java
私有构造函数web
共有静态的getInstance()方法,当静态内部引用为空时才实例化设计模式
多线程环境下不安全安全
考虑到多线程的条件,还有另一种经常使用的简单实现方式:多线程
public class Singleton{
private final static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
private, final 和 static 的实例变量函数
私有化构造函数性能
共有静态的getInstance()方法优化
static 的实例变量在类加载到内存的时候就会初始化,建立实例是线程安全的spa
实例在类初始化一开始就被建立了,哪怕后来根本没有使用它
若是实例的建立时依赖于外部的参数/文件的话,这种方式就不适用了
为了不上面饿汉式的缺点,咱们来考虑改进懒汉式单例模式来支持多线程的状况。最直接的想法就是对 getInstance()
加锁,可是这样一来同一时间只能有一个线程调用单例实例,效率低下。经过分析,咱们能够发现其实不用对整个 getInstance()方法加锁,只须要在实例为空须要建立时加锁。
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
两次检查 instance == null
,一次是在同步块外,一次是在同步块内
使用两次判断的缘由:有可能多个线程同时进入第一个
if
判断,若是在同步块中再也不次判断的话,有可能生成多个实例
因为JVM指令重排序的优化,在instance = new Singleton();
仍有可能生成多个实例
在JVM指令优化时,
instance = new Singleton();
并非一个原子操做,而是3个步骤:
1. 为instance分配内存
2. 调用 Singleton构造函数初始化成员变量
3. 将instance对象指向分配的内存空间 (instance非null)
在JVM编译优化时,上面3个步骤并非顺序执行的,有可能从新排列执行的顺序,有多是 1-2-3, 或者 1-3-2。若是是 1-3-2的执行顺序的话,有可能出现这种状况:线程1执行完了1-3步骤后退出了同步块,这个时候instance已是非null了,但还没被初始化,这个时候线程2进入同步块,判断instance为非null,全部直接返回没有初始化的对象,在后面的使用中天然会报错。
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
- 使用JVM自己机制保证了线程安全问题;
- SingletonHolder 是私有的,除了 getInstance() 以外没有办法访问它,所以它是懒汉式的;
- 读取实例的时候不会进行同步,没有性能缺陷;
- 不依赖 JDK 版本
Java中的五种单例模式实现方法
单例模式的七种写法
单例模式的几种实现方式
如何正确地写出单例模式
Java线程安全的单例模式的几种实现
单例模式的5种形式