单例模式
单例模式(Singleton Pattern) 创建型模式,范畴:对象
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的通用类图
Singleton类称为单例类,通过使用private的构造函数确保了在一个应用中只产生一个实例,并且是自行实例化的
通用代码:
package com.lushunde.desingn.singleton;
/**
* 通用模式-饿汉式
* @author bellus
* */
public class Singleton {
private static final Singleton singleton = new Singleton();
// 限制产生多个对象
private Singleton() {
}
// 通过该方法获得实例对象
public static Singleton getSingleton() {
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
}
实现单例的几种形式
实现方式 | 优点 | 缺点 | 效率 |
---|---|---|---|
饿汉式(通用形式) | 线程安全,效率极高 | 缺少延时加载 | |
懒汉式(方法同步) | 线程安全,能延时加载 | 效率不高(每次加锁) | |
双重检测锁式(volatile) | 线程安全,效率很高,能延时加载 | ||
静态内部类 | 线程安全,效率高,能延时加载 | ||
枚举单例 | 线程安全,效率高 ,能防止反序列化 | 缺少延时加载 |
饿汉式
天然线程安全,无懒加载,效率高
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 通用模式-饿汉式,解决注意事项
* 天然线程安全,无懒加载,效率高
* @author bellus
*
*/
public class Singleton implements Serializable {
private static final long serialVersionUID = -7640246007296163797L;
private static final Singleton singleton = new Singleton();
// 限制产生多个对象
private Singleton() {
//解决反射创建对象,直接抛异常
if(singleton!=null){
throw new RuntimeException("防止反射创建对象");
}
}
// 通过该方法获得实例对象
public static Singleton getSingleton() {
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
//解决反序列化漏洞
public Object readResolve() throws ObjectStreamException{
return singleton;
}
}
饿汉式(加锁)
每次获取都要加锁,影响效率
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 通用模式-懒汉式,解决线程安全、反射漏洞、反序列化
*
* @author bellus
*
*/
public class Singleton2 implements Serializable {
private static final long serialVersionUID = -7640246007296163797L;
private static Singleton2 singleton = null;
// 限制产生多个对象
private Singleton2() {
//解决反射创建对象,直接抛异常
if(singleton!=null){
throw new RuntimeException("防止反射创建对象");
}
}
// 通过该方法获得实例对象+ 加锁解决线程安全问题
public static synchronized Singleton2 getSingleton() {
if(singleton==null){
singleton = new Singleton2();
}
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
//解决反序列化漏洞
public Object readResolve() throws ObjectStreamException{
return singleton;
}
}
双重检查锁
DCL失效问题,通过volatile可以解决。
INSTANCE = new SingleTon();
这个步骤,其实在jvm里面的执行分为三步:
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。
由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static SingleTon INSTANCE = null;就可解决DCL失效问题。volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 双重加锁,解决线程安全、效率、反射漏洞、反序列化
*
* INSTANCE = new SingleTon();
* 这个步骤,其实在jvm里面的执行分为三步:
* 1.在堆内存开辟内存空间。
* 2.在堆内存中实例化SingleTon里面的各个参数。
* 3.把对象指向堆内存空间。
* 由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
* 不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static SingleTon INSTANCE = null;就可解决DCL失效问题。volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅。
* @author bellus
*
*/
public class Singleton3 implements Serializable {
private static final long serialVersionUID = -7640246007296163797L;
//volatile 解决DCL失效
private static volatile Singleton3 singleton = null;
// 限制产生多个对象
private Singleton3() {
// 解决反射创建对象,直接抛异常
if (singleton != null) {
throw new RuntimeException("防止反射创建对象");
}
}
// 通过该方法获得实例对象+ 加锁解决线程安全问题,双重判断 提高效率
public static Singleton3 getSingleton() {
if (singleton == null) {
synchronized (Singleton3.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
// 解决反序列化漏洞
public Object readResolve() throws ObjectStreamException {
return singleton;
}
}
静态内部类
通过类加载机制实现懒加载和单例
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 静态内部类
* 外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存
*
* @author bellus
*
*/
public class Singleton4 implements Serializable {
private static final long serialVersionUID = -7640246007296163797L;
private Singleton4() {
// 解决反射创建对象,直接抛异常
if (SingletonHoler.INSTANCE != null) {
throw new RuntimeException("防止反射创建对象");
}
}
private static class SingletonHoler {
private static Singleton4 INSTANCE = new Singleton4();
}
public static Singleton4 getSingleton() {
return SingletonHoler.INSTANCE;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
// 解决反序列化漏洞
public Object readResolve() throws ObjectStreamException {
return SingletonHoler.INSTANCE;
}
}
枚举
天然的 线程安全、无反射漏洞,无序列化漏洞
package com.lushunde.desingn.singleton.improve;
import java.io.Serializable;
/**
* 枚举
* 默认就 线程安全、无反射漏洞,无序列化漏洞
* @author bellus
*
*/
public enum Singleton5 implements Serializable {
INSTANCE;
// 类中其他方法,尽量是static
public static void doSomething() {
}
}
扩展:有上限的多例模式
含义:
产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,提供系统的响应速度。
常用场景:
-
读取文件,设置多个固定提高并发量,继而提升性能和响应速度。
-
有上限的多例模式代码:
MultitonSingleton 代码:
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;
/**
*
* 扩展:有上限的多例模式 **含义:**
* 产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例,方便系统进行扩展,
* 修正单例可能存在的性能问题,提供系统的响应速度。 **常用场景**: 读取文件,设置多个固定提高并发量,继而提升性能和响应速度。
*
* @author bellus
*
*/
public class MultitonSingleton implements Serializable {
private static final long serialVersionUID = -7640246007296163797L;
// 定义最多能产生的实例的数量
private static int maxNumOfMultiton = 2;
// 定义一个列表,容纳所有实例
private static ArrayList<MultitonSingleton> SingletonList = new ArrayList<MultitonSingleton>();
// 当前实例的序列号
private static int countNumOfMultiton = 0;
// 产生所有对象
static {
for (int i = 0; i < maxNumOfMultiton; i++) {
SingletonList.add(new MultitonSingleton());
}
}
private MultitonSingleton() {
// 解决反射创建对象,直接抛异常
if (SingletonList.size() > maxNumOfMultiton) {
throw new RuntimeException("创建数已达到最大数,防止反射创建对象");
}
}
// 随机获得一个皇帝对象
public static MultitonSingleton getInstance() {
Random random = new Random();
countNumOfMultiton = random.nextInt(maxNumOfMultiton);
return SingletonList.get(countNumOfMultiton);
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
// 解决反序列化漏洞
public Object readResolve() throws ObjectStreamException {
return SingletonList.get(countNumOfMultiton);
}
}
MultitonClient6 代码:
package com.lushunde.desingn.singleton.improve;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
/**
* 有上限的多例模式 测试 正常调用 反射漏洞 反序列化漏洞
*
* @author bellus
*
*/
public class MultitonClient6 {
public static void main(String[] args) {
MultitonSingleton instance = MultitonSingleton.getInstance();
// 多次调用,查看有多少对象
test1();
// 实现反射创建新对象
testReflect(instance);
// 通过反序列化
testSerzi(instance);
}
private static void test1() {
// 调用10次,查看对象
MultitonSingleton instance = null;
for (int i = 0; i < 10; i++) {
instance = MultitonSingleton.getInstance();
System.out.println(instance);
}
}
private static void testReflect(MultitonSingleton instance) {
try {
// 实现反射创建新对象
Class<?> classType = Singleton5.class;
Class<?>[] cArg = new Class[0]; // 入参类型(空参)
// 获取 构造方法 类
Constructor<?> constructor = classType.getDeclaredConstructor(cArg);
// 打开 私有方法开关
constructor.setAccessible(true);
// 创建新对象
MultitonSingleton instance3 = (MultitonSingleton) constructor.newInstance();
System.out.print("反射创建: ");
if (instance == instance3) {
System.out.println("同一个对象");
} else {
System.out.println("不同对象");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void testSerzi(MultitonSingleton instance) {
// 通过反序列化获取对象和一中其中一个相同
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./singleton.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./singleton.txt"))) {
oos.writeObject(instance);
oos.close();
MultitonSingleton instance4 = (MultitonSingleton) ois.readObject();
ois.close();
System.out.println("反序列化获取对象:" + instance4);
} catch (Exception e) {
e.printStackTrace();
}
}
}
单例模式的优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例模式的缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
单例模式的使用场景
- 要求生成唯一序列号的环境;
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
具体场景
- Spring中 bean对象创建,参数scope中有单例模式(默认,Spring容器可以管理这些Bean的生命期;如果采用非单例模式Prototype类型,则Bean初始化后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期)
- servlet中每个servlet实例(Application servlet)
- Spring MVC 和 Struts1 框架中,控制器对象实用单例模式
- 应用程序的日志应用一般都是用单例模式
- 项目中读取配置文件对象
- 操作系统的文件系统、任务管理器、回收站(windows)
- 数据库的连接池(有上限的多例模式)
开发使用场景
- 封装工具类(也可以直接static声明方式)
- 计数器、生成唯一序列等
单例模式的注意事项
1. 单例模式的线程同步问题
饿汉式存在此问题,需要通过加锁synchronized实现线程安全
2. 考虑对象的clone复制实例
解决该问题的最好方法就是单例类不要实现Cloneable接口,此时clone方法不能复制
3. 通过反射创建对象
私有构造器加入非空判断抛异常,这样反射调用时就会抛异常
private Singleton() {
//解决反射创建对象,直接抛异常
if(singleton!=null){
throw new RuntimeException("防止反射创建对象");
}
}
4. 通过反序列化创建对象
重写readresolve()方法,实现反序列化时直接返回已有的实例
//解决反序列化漏洞
public Object readResolve() throws ObjectStreamException{
return singleton;
}
反射和反序列化漏洞代码演示
singleton 代码:
package com.lushunde.desingn.singleton;
import java.io.Serializable;
/**
* 通用模式-饿汉式
*
* @author bellus
*
*/
public class Singleton implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3273287152119518024L;
private static final Singleton singleton = new Singleton();
// 限制产生多个对象
private Singleton() {
}
// 通过该方法获得实例对象
public static Singleton getSingleton() {
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
}
Client 代码
package com.lushunde.desingn.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
/**
* 测试 正常调用
* 反射漏洞
* 反序列化漏洞
* @author bellus
*
*/
public class Client {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
// 调用两次
test1(singleton);
// 实现反射创建新对象
testReflect(singleton);
// 通过反序列化
testSerzi(singleton);
}
private static void test1(Singleton singleton) {
// 调用两次,查看是否同一个对象
Singleton singleton2 = Singleton.getSingleton();
System.out.print("正常创建: ");
if (singleton == singleton2) {
System.out.println("同一个对象");
} else {
System.out.println("不同对象");
}
}
private static void testReflect(Singleton singleton) {
try {
// 实现反射创建新对象
Class<?> classType = Singleton.class;
Class<?>[] cArg = new Class[0]; // 入参类型(空参)
// 获取 构造方法 类
Constructor<?> constructor = classType.getDeclaredConstructor(cArg);
// 打开 私有方法开关
constructor.setAccessible(true);
// 创建新对象
Singleton singleton3 = (Singleton) constructor.newInstance();
System.out.print("反射创建: ");
if (singleton == singleton3) {
System.out.println("同一个对象");
} else {
System.out.println("不同对象");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void testSerzi(Singleton singleton) {
// 通过反序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./singleton.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./singleton.txt"))) {
oos.writeObject(singleton);
oos.close();
Singleton singleton4 = (Singleton) ois.readObject();
ois.close();
System.out.print("反序列化创建: ");
if (singleton == singleton4) {
System.out.println("同一个对象");
} else {
System.out.println("不同对象");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
正常创建: 同一个对象
反射创建: 不同对象
反序列化创建: 不同对象
反射和反序列化漏洞修复后代码演示
只需要修改单例代码:
package com.lushunde.desingn.singleton.improve;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 通用模式-饿汉式,解决注意事项
*
* @author bellus
*
*/
public class Singleton implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7640246007296163797L;
private static final Singleton singleton = new Singleton();
// 限制产生多个对象
private Singleton() {
//解决反射创建对象,直接抛异常
if(singleton!=null){
throw new RuntimeException("防止反射创建对象");
}
}
// 通过该方法获得实例对象
public static Singleton getSingleton() {
return singleton;
}
// 类中其他方法,尽量是static
public static void doSomething() {
}
//解决反序列化漏洞
public Object readResolve() throws ObjectStreamException{
return singleton;
}
}
再次调用client结果如下:
正常创建: 同一个对象
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.lushunde.desingn.singleton.improve.Client.testReflect(Client.java:54)
at com.lushunde.desingn.singleton.improve.Client.main(Client.java:26)
Caused by: java.lang.RuntimeException: 防止反射创建对象
at com.lushunde.desingn.singleton.improve.Singleton.<init>(Singleton.java:25)
... 6 more
反序列化创建: 同一个对象