设计模式之单例模式
1.单例模式介绍
1.1背景
因为有些对象只需要一个,比如线程池、缓存、对话框等程序对象,如果创建出多个实例就会导致许多问题的产生,例如:程序行为异常,资源使用过量,或者不一致的结果等。为了解决这一问题,单例模式出现了。
1.2优点
只创建一个实例,并且在使用的时候才创建,处理非常耗资源且的对象非常合适,因为如果在启动时就创建耗资源的对象,但是在程序开始执行到使用它之前这段时间又一直没有使用它,那就非常浪费资源了。同时也能避免一个全局使用的类频繁地创建与销毁。
1.3缺点
没有接口,无法继承,违背了类的单一职责原则,一个类只应该做一件事。它不止负责管理自己的实例(并提供全局访问),还在应用程序中承担角色。
1.4应用场景
配置文件访问类,不用每次使用时都new一个。
数据库连接池 保证项目中只有一个连接池存在。
2.单例模式五种实现
2.1饿汉式
/*
* @author smilesboy
* @Date 2020/5/10
* 饿汉式
*/
public class Singleton {
/**
* 类变量在类准备阶段就初始化了然后放在<clinit>构造方法中
* 一旦外部调用了静态方法,那么就会初始化完成。
* 一个类的<clinit>只会执行一次 保证多线程情况下不会创建多个实例
*/
private static final Singleton INSTANCE = new Singleton();
/*
* 构造函数私有化
*/
private Singleton() {}
/**
* 提供公共方法以获取实例对象
* @return instance 实例对象
*/
public static Singleton getInstance() {
return INSTANCE;
}
}
这种方式创建的单例模式,类加载是就创建,由classloder保证了线程安全。
2.2静态内部类
/*
* @author smilesboy
* @Date 2020/5/10
* 静态内部类
*/
public class Singleton {
private static class SingletonHolder {
/**
* 类变量在类准备阶段就初始化了然后放在<clinit>构造方法中
* 一旦外部调用了静态方法,那么就会初始化完成。
* 一个类的<clinit>只会执行一次 保证多线程情况下不会创建多个实例
*/
private static final Singleton INSTANCE = new Singleton();
}
/*
* 构造函数私有化
*/
private Singleton() {}
/**
* 提供公共方法以获取实例对象
* @return instance 实例对象
*/
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式实现的单例模式,实现了类使用时才加载,由classloder保证了线程安全。
饿汉式/静态内部类是如何保证线程安全的,在《深入理解JAVA虚拟机》中,有这么一句话:
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
2.3懒汉式
/*
* @author smilesboy
* @Date 2020/5/10
* 懒汉式
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
/**
* synchronized 保证线程安全 但效率低
*
* @return instance单例对象
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种方式实现的单例模式,实现了使用类时才创建,使用synchronized保证线程安全,但效率低。
2.4双重校验锁
/*
* @author smilesboy
* @Date 2020/5/10
* 双重校验锁
*/
public class Singleton {
/**
* volatile关键字禁止指令重排序
* 保证多线程下不会获取到未完全初始化的实例
*/
private volatile static Singleton instance;
private Singleton() {}
/**
* 双重if校验 缩小synchronized代码块范围
* 若instance不为空 就可直接return
*
* @return instance 实例对象
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
//非原子操作
instance = new Singleton();
}
}
}
return instance;
}
}
这种方式实现的单例:实现了类使用时才创建实例。synchronized保证了线程安全,volatile禁止指令重排序保证了多线程获取时不为空,JDK1.5以上才行。具体细节请参考volatile关键字在单例模式(双重校验锁)中的作用
2.5枚举
/*
* @author smilesboy
* @Date 2020/5/10
* 枚举式
*避免反射攻击
* 序列化及反序列化安全
*/
enum EnumSingleton {
/**
* 定义一个枚举的元素,它就是singleton的一个实例
*/
INSTANCE;
public void doOtherThing() {
System.out.println("枚举生成单例模式");
}
}
public class Singleton {
public static void main(String[] args) {
EnumSingleton singleton = EnumSingleton.INSTANCE;
singleton.doOtherThing();
}
}
这种方式也是《Effective Java 》的作者推荐的方式。为什么会推荐这种方式呢?因为前面4种都存在一个序列化和反序列化时的安全问题。将单例对象序列化后,在反序列化时会重新创建一个单例对象,违背了单例模式的初衷。而枚举式单例则没有这个问题。详细信息请查看为什么要用枚举实现单例模式(避免反射、序列化问题)
当我们使用单例模式时可以根据不同场合选择具体的实现方式,一般情况下比较常使用的是静态内部类或者双重校验锁方式。

浙公网安备 33010602011771号