前言
主要讲解单例模式的几种写法,以及每种写法的区别优劣势
这一模式的目的是使得类的一个对象成为系统中的唯一实例
1. 定义
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
单例模式的例子
public class Singleton {
private Singleton(){
}
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
所谓单例模式的三要素:
一是某个类只能有一个实例
二是它必须自行创建这个实例
三是它必须自行向整个系统提供这个实例
2. 实现
一共有6种实现方法
2.1 懒汉式(线程不安全)
先不创建实例,当被调用的时候,再创建实例
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
优点:延迟实例化,没被用到的话节约了系统资源。
缺点: 线程不安全的,并发环境下很可能出现多个Singleton实例,未实例化就进行实例操作
2.2 饿汉式(线程安全)
不管用不用到,先实例化
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
优点: 提前实例,只有一个实例,避免线程不安全
缺点: 没用到的话,会造成资源浪费
2.3 懒汉式(线程安全)
在get方法上加了一把锁。如果多个线程访问,只有进入线程拿到锁,才可执行,避免线程不安全
public class Singleton {
private static Singleton uniqueInstance;
private static singleton() {
}
private static synchronized Singleton getUinqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
优点: 延迟实例化,节约了资源,并且是线程安全的。
缺点: 虽然解决了线程安全问题,但是性能降低了。有了锁,进入该方法的会使线程阻塞,等待时间过长
2.4 双重检查锁实现(线程安全)
双重检查锁改进懒汉式(线程安全)
改进方法是:将锁的位置改变,并且多加一个检查。
先判断实例是否已经存在,在判断没有实例化的时候,多个线程进去了,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题
解决办法就是加一个 volatile 关键字修饰 uniqueInstance ,volatile 会禁止 JVM 的指令重排,就可以保证多线程环境下的安全运行
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
优点: 延迟实例化,节约了资源;线程安全;
缺点: volatile 关键字,对性能也有一些影响。
2.5 静态内部类实现(线程安全)
外部类被加载时,静态内部类并没有被加载进内存
当调用 getInstance方法时,会运行LazyHolder.INSTANCE; 此时静态内部类LazyHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次
优点: 延迟实例化,节约了资源;且线程安全;性能也提高了
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
2.6 枚举类实现(线程安全)
默认枚举实例的创建就是线程安全的,且在任何情况下都是单例
public enum Singleton {
INSTANCE;
//添加自己需要的操作
public void doSomeThing() {
}
}
优点:防止反射和反序列化调用
关于序列化可看我之前的文章
java之序列化与反序列化的详细解析(全)
简单的来说:
序列化:java对象-》字节码序列。
反序列化:字节码序列-》java对象
要想防止多个对象,也就是一个实例,即反序列化只能有一次
也就是实例对象重写这个方法
private Object readResolve() throws ObjectStreamException{
return singleton;
}
3. 总结
- 饿汉式,直接在方法外定义(线程安全,调用效率高,但是不能延时加载);
- 懒汉式,加了锁,方法内实例化(线程安全,调用效率不高,
但是能延时加载
); - Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用);
- 静态内部类实现模式(线程安全,调用效率高,
可以延时加载
); - 枚举类(线程安全,调用效率高,
不能延时加载
,可以天然的防止反射和反序列化调用)。
所谓的延时不延时加载,主要看加载这个类的时候会不会出来这个实例
而枚举类一加载,就会出来。饿汉式定义在方法外,不管用没用直接加载,所以不能延时加载
具体使用的场景总结:
- 频繁实例化又销毁的对象
- 经常使用的对象如数据库连接池,使用单例模式,可以提高性能,降低资源损坏。
- 使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信。