设计模式详解之单例模式
前言
提示:软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
提示:以下是本篇文章正文内容,下面案例可供参考
一、懒汉式
- 相较于饿汉式,第一次获取对象时为空,需要创建对象。
- 由于使用了Double check和synchronized,对性能稍微有写损耗。
- 代码展示
/**
* 懒汉模式
* 1、volatile是为了保证新创建的对象可见和防止指令重排序
* //字节码层面
* 1、分配空间
* 2、初始化
* 3、引用复制
* 如果不加volatile,第二步和第三步的顺序可以是不固定的。如果不固定有可能在第二检查对象是否未空时出现问题。
* 若第二步和第三步顺序反了,第一个线程创建好对象,先复制,后一个线程获取到锁,判断到对象为空,对象将会被改变。
* 2、双重检查避免锁的问题
*/
public class LazySingleton implements Serializable {
static final long serialVersionUID = 42L;
private volatile static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized(LazySingleton.class) {
/**
* 1、再此检查是为了防止并发
* 如果多个线程刚开始都在等待锁,一个线程获取到锁之后对象实例化完成,
* 后面线程获取到线程会继续实例化,会导致对象不一致问题。
*/
if ( instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
public Object readResolve() throws ObjectStreamException {
return instance;
}
}
class LazySingletonTest {
public static void main(String[] args) throws Exception {
test2();
}
/**
* 如果需要保证反序列化后的对象和单例对象一致
* 1、首先对象实现Serializable接口
* 2、写一个Object readResolve() throws ObjectStreamException方法
* @throws Exception
*/
public static void test3() throws Exception {
LazySingleton instance = LazySingleton.getInstance();
//将实例化的对象保存到磁盘中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LazySingleton"));
oos.writeObject(instance);
//将磁盘中的对象转为实例对象
ObjectInputStream ios = new ObjectInputStream(new FileInputStream("LazySingleton"));
LazySingleton object = ((LazySingleton) ios.readObject());
//判断两个对象是否为同一个对象
System.out.println(instance == object);
}
/**
* 测试反射情况
* 懒汉模式无法保证反射对象与单例对象是同一个对象
*/
public static void test2() throws Exception {
LazySingleton instance = LazySingleton.getInstance();
Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton = constructor.newInstance();
//判断懒汉模式能否防止反射获取对象
System.out.println(instance == lazySingleton);
}
/**
* 测试并发、指令重排问题
*/
public static void test1() {
List<LazySingleton> lists = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i=0; i<20; i++) {
executorService.execute(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
if (!lists.contains(instance)) {
lists.add(instance);
}
});
}
executorService.shutdown();
System.out.println(lists.size());
lists.forEach(lazySingleton -> System.out.println(lazySingleton));
}
}
二、饿汉模式
- 饿汉模式在初始化对象的时候就已经实例化好对象了,相较于懒汉模式,性能会高一些。
- 代码展示
/**
* 饿汉模式
* 类加载的 初始化阶段就完成了 实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。
* 类加载过程:
* 1、加载二进制数据到内存中,生成对应的Class数据结构。
* 2、连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析
* 3、初始化:给类的静态变量赋初值
* 只有在真正使用对应的类时,才会出发初始化(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等)
*/
public class HungrySingleton implements Serializable {
static final long serialVersionUID = 42L;
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
//防止通过反射方式创建对象
if (instance !=null) {
throw new RuntimeException("单例模式下不允许有多个对象");
}
return instance;
}
public Object readResolve() throws ObjectStreamException {
return instance;
}
}
class HungrySingletonTest {
public static void main(String[] args) throws Exception {
test2();
}
/**
* 测试反序列化获取对象
* 可以保证反序列化和单例对象一致
*/
public static void test3() throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
//序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("HungrySingleton"));
oos.writeObject(instance);
//反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("HungrySingleton"));
HungrySingleton hungrySingleton = (HungrySingleton) ois.readObject();
System.out.println(instance == hungrySingleton);
}
/**
* 测试反射获取单例对象
* 无法保证
*/
public static void test2() throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton hungrySingleton = constructor.newInstance();
System.out.println(hungrySingleton == instance);
}
/**
* 测试并发情况下
*/
public static void test1() {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
executorService.execute(() -> {
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
});
}
executorService.shutdown();
}
}
三、静态内部类
- 代码展示
/**
* 静态内部类单例对象
*/
public class StaticInnerClassSingleton implements Serializable {
static final long serialVersionUID = 42L;
private StaticInnerClassSingleton(){}
public static StaticInnerClassSingleton getInstance() {
if (innerStaticInnerClassSinleton.instance != null) {
//防止反射来创建对象
throw new RuntimeException("单例不允许多个实例");
}
return innerStaticInnerClassSinleton.instance;
}
private static class innerStaticInnerClassSinleton{
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public Object readResolve() throws ObjectStreamException{
return innerStaticInnerClassSinleton.instance;
}
}
class StaticInnerSingletonTest{
public static void main(String[] args) throws Exception {
test2();
}
/**
* 静态内部类可以保证反序列化后的对象和单例对象一致
* @throws Exception
*/
public static void test3() throws Exception {
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
//序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("StaticInnerClassSingleton"));
oos.writeObject(instance);
//反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("StaticInnerClassSingleton"));
StaticInnerClassSingleton staticInnerClassSingleton = (StaticInnerClassSingleton) ois.readObject();
System.out.println(instance == staticInnerClassSingleton);
}
/**
* 测试反射情况
* 静态内部类无法保证反射对象与单例对象一至
* @throws Exception
*/
public static void test2() throws Exception{
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
Constructor<StaticInnerClassSingleton> constructor = StaticInnerClassSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
StaticInnerClassSingleton staticInnerClassSingleton = constructor.newInstance();
System.out.println(instance == staticInnerClassSingleton);
}
/**
* 并发情况
*/
public static void test1() {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i=0; i< 20; i++) {
executorService.execute(()->{
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
System.out.println(instance);
});
}
executorService.shutdown();
}
}
四、枚举类型
- 代码展示
public enum EnumSingleton{
INSTANCE;
public void print() {
System.out.println(INSTANCE.hashCode());
}
}
class EnumSingletonTest{
public static void main(String[] args) throws Exception {
test3();
}
//枚举类型可以保证反序列化的对象和单例对象一致
public static void test3() throws Exception {
EnumSingleton instance = EnumSingleton.INSTANCE;
//序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("EnumSingleton"));
oos.writeObject(instance);
//反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("EnumSingleton"));
EnumSingleton enumSingleton = (EnumSingleton) ois.readObject();
System.out.println(enumSingleton == instance);
}
//枚举类型无法通过反射创建单例对象
public static void test2() throws Exception {
EnumSingleton instance = EnumSingleton.INSTANCE;
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
constructor.newInstance("INSTANCE", 0);
}
//测试并发情况
public static void test1() {
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i=0; i<20;i++) {
executorService.execute(()->{
EnumSingleton.INSTANCE.print();
});
}
executorService.shutdown();
}
}
五、四种方式创建单例对象对比
懒汉模式 | 饿汉模式 | 静态内部类模式 | 枚举模式 | |
---|---|---|---|---|
并发情况 | ✔ | ✔ | ✔ | ✔ |
反射获取对象 | ✔ | ❌ | ❌ | ✔ |
反序列化获取对象 | ✔ | ✔ | ✔ | ✔ |
- 懒汉模式通过public Object readResolve() 来保证反射的对象和单例对象一致.
- 饿汉模式、静态内部类模式、枚举模式都是借助JVM来保证并发情况下对象一致。
总结
以上就是个人对设计模式中的单例模式的详解,有不足或错误的地方望在评论区指出。