什么是单例模式
单例类在整个程序中只能有一个实例,这个类负责创建自己的对象,饼确保只有一个对象被创建
单例实现的要点
- 私有构造器
- 持有该类的属性
- 对外提供获取实例的静态方法
优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
单例的几种实现方式
1.饿汉式
- 线程安全,反射不安全,反序列化不安全
public class Singleton{
private static Singleton instance = new Singleton();
// 私有构造方法
private Singleton(){}
// 公共获取实例方法
public static Singleton getInstance(){
return instance;
}
// 解决序列化,反序列化不安全
private Object readResolve(){
return instance;
}
}
2.登记式(静态内部类)
- 线程安全,防止反射攻击,反序列化不安全
public class Singleton{
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private Singleton(){
// 防止反射攻击
if(SingletonHolder != null){
// 如果对象已经创建了,此时就不需要再调用此方法
throw Exception();
}
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
// 解决反序列化不安全
private Object readResolve(){
return instance;
}
}
3.枚举式
- 线程安全,支持序列化、反序列化安全,防止反射攻击
public enmu Singleton{
INSTANCE{
@Override
protected void doSomething(){
...
}
}
protected abstract void doSomething();
}
4.懒汉式
- 线程不安全,延迟加载(两种同步,效率低)
// 非线程安全
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public Singleton getInstance(){
if(instace == null){
return new Singleton();
}
return instance;
}
}
// 线程安全-同步方法
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
return new Singleton();
}
return instance;
}
}
// 线程安全-同步代码块
public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
}
5.双检索
- 线程安全,volatile
public class Singleton{
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
此时,这是否能保证双检索单例就一定没问题吗?并不是,会有可能出现指令重排的问题
指令重排
Singleton instance = new Singleton()会执行如下操作:
1.分配对象内存空间
2.初始化对象
3.instance指向1中分配的内存空间
在某些编辑器上可能出现指令重排:
1.分配对象内存空间
2.instance指向1中分配的空间(但此时对象没有初始化)
3.初始化对象
为了解决这一问题,需要避免指令重排,此时我们使用volatile关键字
public class Singleton{
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}