1.简介
单例模式是软件开发中常用的一种设计模式,它要确保某个类在系统中只有唯一的一个实例
2.应用场景
a.共享资源,保证数据的一致性,如配置文件
b.创建对象耗时或者消耗资源过多,但又经常使用到,如数据库连接池、线程池等
c.工具类对象
3.实现方式
a.饿汉式
public class SingletonOne {
private static final SingletonOne singletonOne = new SingletonOne();
private SingletonOne() {
if (singletonOne != null) {
// 防止反射获取多个对象的漏洞
throw new RuntimeException("instance has init");
}
}
public static SingletonOne getInstance() {
return singletonOne;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return singletonOne;
}
}
优点:
类加载时完成对象实例化,避免多线程下的同步问题,同时获取单例速度较快
缺点:
资源利用效率不高,单例可能一直用不到,但是已经初始化了,造成资源浪费
b.懒汉式
public class SingletonTwo {
private static SingletonTwo singletonTwo = null;
private SingletonTwo() {
if (singletonTwo != null) {
throw new RuntimeException("instance has init");
}
}
public static SingletonTwo getInstance(){
if (singletonTwo == null) {
singletonTwo = new SingletonTwo();
}
return singletonTwo;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return singletonTwo;
}
}
优点:
懒加载,第一次调用时实例化对象,资源利用率高,后续调用速度快
缺点:
多线程场景下,会产生多个对象
c.懒汉式+锁
public class SingletonThree {
private static SingletonThree singletonThree = null;
private SingletonThree() {
if (singletonThree != null) {
throw new RuntimeException("instance has init");
}
}
public static SingletonThree getInstance() {
synchronized (SingletonThree.class) {
if (singletonThree == null) {
singletonThree = new SingletonThree();
}
}
return singletonThree;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return singletonThree;
}
}
优点:
通过加锁解决多线程场景下产生多个实例的问题
缺点:
加锁后,每次调用方法都需要获取锁,影响性能,其实只需要在第一次实例化对象时调用对象
d.双重检查锁(DCL)
public class SingletonFour {
private static SingletonFour singletonFour = null;
private SingletonFour() {
if (singletonFour != null) {
throw new RuntimeException("instance has init");
}
}
public static SingletonFour getInstance() {
if (singletonFour == null) {
synchronized (SingletonFour.class) {
if (singletonFour == null) {
singletonFour = new SingletonFour();
}
}
}
return singletonFour;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return singletonFour;
}
}
优点:
未实例化时才会去竞争锁来实例化,实例化后直接返回实例对象,提升性能同时解决多线程的同步问题
缺点:
由于jvm的指令重排问题,在多线程场景下会引用一个未初始化完成的对象,导致系统出现问题,具体原因如下:对象创建时,其实是分三步完成的,1)在堆上给对象分配内存,2)调用构造方法初始化此对象,3)将对象引用指向对应内存地址,由于jvm的指令重排机制,这三步可能不是顺序执行的,如果是1)3)2)顺序,3)完成2)还没完成时,另一个线程判断对象不为null会直接返回未初始化完全的对象,导致系统出现问题
e.双重检查锁(优化)
public class SingletonFour {
private static volatile SingletonFour singletonFour = null;
private SingletonFour() {
if (singletonFour != null) {
throw new RuntimeException("instance has init");
}
}
public static SingletonFour getInstance() {
if (singletonFour == null) {
synchronized (SingletonFour.class) {
if (singletonFour == null) {
singletonFour = new SingletonFour();
}
}
}
return singletonFour;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return singletonFour;
}
}
优点:
在双重检查锁的基础增加了volatile关键字,禁止指令重排,避免出现引用未初始化完成的对象
缺点:
禁用指令重排后,jvm的一些代码优化措施会无效,影响性能
f.内部静态类
public class SingletonFive {
private SingletonFive() {
// 防止反射获取多个对象的漏洞
if (Singleton.singleton != null) {
throw new RuntimeException("instance has init");
}
}
private static class Singleton {
private static final SingletonFive singleton = new SingletonFive();
}
public static SingletonFive getInstance() {
return Singleton.singleton;
}
// 防止反序列化获取多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return Singleton.singleton;
}
}
推荐使用
懒加载,线程安全
内部类不会随着外部类初始化,只会在调用getInstance时才会初始化,而由于类的初始化<clinit>()会被加锁,所以也是线程安全的
g.枚举
public enum SingletonSix {
singleton;
}
推荐使用
只需调用SingletonSix. singleton,写法简单且线程安全,还能防止反序列化重新创建对象,只会存在一个实例
4.结语
单例模式实现方式常见有以上这几种,推荐使用内部静态类和枚举的方式实现单例。同时在使用过程中要注意避免反射和反序列化创建多个对象