单例模式是常见的一种设计模式,通过单例模式创建的类在当前进程中只有一个实例。
Java中单例模式的定义:“一个类有且只有一个实例,并且自行实例化向整个系统提供。”
1.单例模式的特点
1)单例类只能有一个实例;
2)单例类自行创建唯一实例;
3)单例类须向所有其他对象提供该实例。
2. 常见实现方法
1. 饿汉式-线程安全
优点:简单,不存在线程安全问题。
缺点:浪费资源(未调用时也会创建出实例)。
//饿汉式
public class HungryMan {
//初始化
private static HungryMan instance = new HungryMan();
//私有化构造方法
private HungryMan(){}
//向外提供方法获取实例
public static HungryMan getInstance(){
return instance;
}
}
2. 懒汉式-非线程安全
优点:解决了浪费资源的问题,在调用时才创建出实例。
缺点:非线程安全。
//懒汉式-非线程安全
class LazyMan{
private static LazyMan instance;
private LazyMan(){}
public static LazyMan getInstance(){
if(instance == null) {
instance = new LazyMan();
}
return instance;
}
}
3. 懒汉式-线程安全
优点:线程安全
缺点:效率低,多线程模式下线程只能一个个访问getInstance方法。
//懒汉式-线程安全
class LazyMan{
private static LazyMan instance;
private LazyMan(){}
public synchronized static LazyMan getInstance(){
if(instance == null) {
instance = new LazyMan();
}
return instance;
}
}
4. 双重验证
优点:该模式在多线程环境下仍能保持高性能。
缺点:并非绝对安全,可以通过反射来破坏。
//双重校验
class LazyMan {
/** 为什么要使用valatile关键字?
* instance = new LazyMan(); 可以拆解为三步
* 1. 分配内存
* 2. 初始化对象
* 3. 指向刚分配的地址
* 假设 A 线程执行了1和3,还未执行2,此时B线程来判断NULL,B就会返回还未初始化的instance。
*/
private volatile static LazyMan instance;
private LazyMan() {
}
public static LazyMan getInstance() {
//首次判空,目的是提高效率,如果instance不为空则直接返回,不需要去获取锁
if (instance == null) {
//加锁,防止多线程同时创建实例
synchronized (LazyMan.class) {
//二次判空,防止等待锁的线程重复创建实例
if (instance == null)
instance = new LazyMan();
}
}
return instance;
}
}
如何防止反射破坏该模式?
只需在该类的内部添加方法来阻止序列化生成新的对象。
class LazyMan implements Serializable {
private volatile static LazyMan instance;
private LazyMan() {
}
public static LazyMan getInstance() {
if (instance == null) {
synchronized (LazyMan.class) {
if (instance == null)
instance = new LazyMan();
}
}
return instance;
}
private Object readResolve(){
//防止序列化生成新的实例
return instance;
}
}