确保一个类只有一个实例,并提供全局访问点。
- 懒汉式 - 线程不安全
- 饿汉式 - 线程安全
- 懒汉式 - 线程安全
- 双重校验所 - 线程安全
至于为什么会有懒汉、饿汉这样的名字,我也不明白...
首先说下为什么需要单例模式?
简单来说有些场景下我们并不希望也不需要某个类同时出现多个实例,比如:当前用户的实例。当用户登录了某个APP后,我们并不只是验证完用户名和密码就完事了,还希望能够记录用户的状态,例如用户收藏或点赞了某个动态,这些信息会被同步到服务器,用户也能在“我的收藏”、“我的点赞”中看到自己的新增的记录。这种情况如果能够始终保存当前用户信息是很有用的,系统能够根据当前用户的ID十分方便的去同步当前用户在云端的各种表。
一、单线程基本形式(懒汉式-线程不安全)
public class Singleton {
private static Singleton uniqueInstance;
(1) private Singleton () { }
(2) public static Singleton getInstance () {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
(1)
处将构造方法设为private
,表明只有该类自己可以访问,在类外无法去实例化。
(2)
处采用静态方法,使得类外可以使用Singleton.getInstance()
来获取该类的实例化对象。同样静态方法对应的静态变量保证了该变量能够长期存活。
上述代码在单线程环境下能够很好的运行,但是在多线程环境下则会出问题:
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
当多个线程同时获取实例且实例还为null的时候,可能会多次进入该语句实例化该实例,违反了一个类只有一个实例的原则。
为了避免出现这样的问题,有多种实现方法:
二、直接实例化(饿汉式-线程安全)
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return uniqueInstance;
}
直接实例化直接避免了是否已经实例化的判断,但是没有了延迟实例化的好处,过早地实例化增加了不必要的开销。
三、改成同步方法(懒汉式-线程安全)
public class Singleton {
private static Singleton uniqueInstance;
private Singleton () { }
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
同步方法迫使每个线程在进入这个方法之前,要先等侯别的线程离开该方法。但是这样的做法会引入严重的性能问题:只在第一次实例化的时候这个同步机制才发挥了作用,在以后的获取实例时反倒影响了各个线程对实例的使用,多了不必要的等待。
四、双重检查锁 - 线程安全
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() { }
private static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行
判断两次uniqueInstance == null
的原因是因为可能多个线程都在uniqueInstance == null
的情况下进入了条件语句,如果在里面不再判断一次的话,对实例化语句的同步就没有起到作用,只是延缓了后一个线程的执行。
参考资料
- 《Head First设计模式》
- CSNOTE
网友评论