【设计模式-单例模式】
上一篇写了建造者模式,不知道你们看懂了没,反正我是理解了。
今天来说一下同样属于创建型模式的单例模式,相信这个模式普遍都清楚,因为平时在编码的时候都会进行相应的使用,我这边就当做日志记录一下。免得以后忘了还得去搜,我发现我记忆里非常差,很多东西很快就忘记了,年纪大了没办法。
一、定义
保证一个类仅有一个实例,并提供全局访问点。就是打死也不会生成第二个实例。一般用在工具类上,可以降低内存消耗
二、实例
单例模式有几种,分别是饿汉式、懒汉式、静态内部类单例以及枚举类单例。
(1)饿汉式
顾名思义,饿汉式就是整个项目在启动的时候进行创建。此时可以使用静态代码块。
public class HungrySingleton{
private final static HungrySingleton instance;
static {
instance = new HungrySingleton();
}
public static HungrySingleton getInstance() {
return instance;
}
}
此时外部还是可以通过new的方式创建一个实例化对象,只需要添加一个私有的构造方法,外部就无法通过new实例化对象。
private HungrySingleton() {
}
但是,注意,此时依然是可以通过反射进行构造对象的,例如:
Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
Constructor<HungrySingleton> constructor = hungrySingletonClass.getDeclaredConstructor(null);
1、打开私有方法的权限
constructor.setAccessible(true);
HungrySingleton hungrySingleton = constructor.newInstance();
System.out.println(hungrySingleton == HungrySingleton.getInstance());
运行结果如下:
这个方法好像只能通过枚举类单例才能解决。
此时还是会有一个问题,当单例对象进行序列化之后,通过反序列化出来的结果是不一样的。
比如:
1、这个类需要实现Serializable才能进行序列化
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
2、序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serial_single"));
oos.writeObject(hungrySingleton);
3、反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serial_single"));
HungrySingleton unserialInstance = (HungrySingleton) ois.readObject();
4、查看原有类与反序列化后生成的类是否相同
System.out.println(hungrySingleton == unserialInstance);
结果是false
此时需要在HungrySingleton 类中实现readResolve方法。
private Object readResolve(){
return instance;
}
此时再看上述代码的运行结果
此时反序列化之后就是相同的了,这是为啥来,看一下源码:
点进ObjectInputStream 的readObject方法,然后进入Object obj = readObject0(false);这个方法。
此时会有一个判断读取对象类型的switch代码块,我们的是object类,所以进入readOrdinaryObject方法
而isInstantiable这个方法是只要实现了serializable就返回true
可以看到,这里会根据读取的class去实例化一个新的对象。
但是,下面还有一段代码,看这个desc.hasReadResolveMethod()方法,这个就是判断有没有实现readResolve方法的
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
而这个readResolveMethod字段是一个Method类型的,其在ObjectStreamClass构造方法中。
所以只要实现了readResolve方法就可以反序列化出一样的对象。
(2)懒汉式
懒汉式的意思是只有需要用到这个类的时候才会进行创建。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance() {
1、if (instance == null) {
2、instance = new LazySingleton();
}
return instance;
}
}
这种的话就会有线程不安全的情况,比如线程A跑到1处,线程B跑到2处还没进行实例化的时候,线程A依然会进入if代码块进行实例化,此时会产生两个实例化对象。
此时可以进行加锁:用synchronized 关键字
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
这样的话会影响性能,因为把整个方法都锁定了,当线程A获取锁之后,其他线程就只能等待线程A把锁释放。
这个时候可以只对实例化的代码块加锁,这就是双层锁定了,例如:
public static LazySingleton getDoubleCheckInstance() {
1、if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
2、instance = new LazySingleton();
}
}
}
return instance;
}
此时又会有一个问题,在instance = new LazySingleton();这行代码会发生指令重排的现象,创建实例化对象的过程是有三步,
1、分配内存
2、初始化对象
3、将堆内对象的地址赋值给instance
第2和第3是没有前后关系的,可以先执行第3步。
这个就会发生这种情况,线程A获取锁进来了,在代码2处实例化对象时先将地址赋值给instance,此时instance就不是空对象了,其他线程刚好进入代码1处,instance不为null,此时就会直接返回。
这个情况的解决方法是volatile关键字,其可以禁止指令重排。只需在单例对象加上此关键字。
private static volatile LazySingleton instance = null;
(3)静态内部类单例模式
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton
= new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
}
(4)枚举类单例
public enum EnumSingleton {
INSTANCE;
private String data;
EnumSingleton() {
this.data = new String("abc");
}
public String getInstance () {
return data;
}
}
枚举类是无法通过反射去实例化对象的
Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
Constructor<EnumSingleton> constructor = enumSingletonClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingleton hungrySingleton = constructor.newInstance();
上述代码运行之后会抛异常:
而且枚举类天然是序列化和反序列化是一致的
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serial_single"));
oos.writeObject(enumSingleton);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serial_single"));
EnumSingleton unserialInstance = (EnumSingleton) ois.readObject();
System.out.println(enumSingleton == unserialInstance);
上述代码运行结果是:
所以综合上述所讲,枚举类单例是最优的解决方案。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C语言——for循环和while循环的效率区别——类似哨兵思想
- 机器学习(八)—Apriori算法
- 机器学习(九)—FP-growth算法
- LeetCode — (1)
- Django初体验——搭建简易blog
- Python开发简单记事本
- 在stm32开发可以调用c标准库的排序和查找 qsort bsearch
- Python解析excel文件并存入sqlite数据库
- python学习总结
- C语言calloc()函数:分配内存空间并初始化——stm32中的应用
- 提升代码的运算速度——代码优化的方法总结
- 自己实现sizeof+大小端测试
- 写一个程序检查一个整数是2的幂
- 持续部署入门:基于 Kubernetes 实现滚动发布
- Python源码分析(一)