/**
* @Des: 饿汉模式
*/
public class SingleTest01 {
// 静态变量系统启动就会被加载
private static SingleTest01 singleTest = new SingleTest01();
// 私有化构造方方
private SingleTest01(){
System.out.println("SingleTest01 init ");
}
// 返回对象
public static SingleTest01 newSingleTest(){
return singleTest;
}
}
/**
* @Des: 懒汉模式
*/
public class SingleTest02 {
// 静态变量保存对象
private static SingleTest02 singleTest = null;
// 私有化构造方法
private SingleTest02(){
System.out.println("SingleTest02 init ");
}
// synchronized 修饰方法保证并发安全
public static synchronized SingleTest02 newSingleTest() {
if (singleTest == null){
singleTest = new SingleTest02();
}
return singleTest;
}
}
/**
* @Des: 懒汉模式
*/
public class SingleTest03 implements Serializable {
private static final long serialVersionUID = 1L;
public String name;
// 静态变量保存对象, volatile 保证每个线程读取最新的数据
private static volatile SingleTest03 singleTest = null;
// 私有化构造方法
private SingleTest03(){
System.out.println("SingleTest03 init ");
}
// 获取实例对象
public static SingleTest03 newSingleTest() {
if (singleTest == null){
// singleTest 为空,表示没有初始化,将当前类加锁。
synchronized(SingleTest03.class){
// 双重校验,避免第一个if之后有多个线程在等待。
if (singleTest == null){
singleTest = new SingleTest03();
}
}
}
return singleTest;
}
}
/**
* @Des: 懒汉模式
*/
public class SingleTest04 {
private static class SingletonInstance{
private final static SingleTest04 SINGLETON = new SingleTest04();
}
private SingleTest04(){
System.out.println("SingleTest04 init ");
}
// 获取实例对象
public static SingleTest04 newSingleTest() {
return SingletonInstance.SINGLETON;
}
}
/**
* @Des: 枚举实现单例
*/
public enum SingletonEnum {
// Singleton的单例对象,枚举被加载时,由JVM创建,线程安全
INSTANCE;
// 单例对象中的方法
public void print(){
System.out.println(this.hashCode());
}
}
测试类:
class Test{
public static void main(String[] args) {
for (int i =0; i<10;i++){
new Thread(new Runnable() {
public void run() {
SingletonEnum singleton = SingletonEnum.INSTANCE;
singleton.print();
}
}).start();
}
}
}
上述的 饿汉模式 和 懒汉模式 真的就可以保证单例吗?
下面我们用 反射 和 序列化 对SingleTest01测试一下:
反射生成新的对象代码如下:
public class TestReflection {
@SneakyThrows
public static void main(String[] args) {
// 获取 Test01 Test03 无参构造方式
Constructor constructorTest01 = SingleTest01.class.getDeclaredConstructor();
// 设置为可访问
constructorTest01.setAccessible(true);
// 各自单例对象
SingleTest01 singleTest01 = SingleTest01.newSingleTest();
// 通过无参构造新的对象
SingleTest01 newSingleTest01 = constructorTest01.newInstance();
// 校验两个对象是否相等
System.out.println("singleTest01 是否等于 newSingleTest01 :" + (singleTest01 == newSingleTest01));
}
}
执行结果如下:
如结果所示, 我们的单例被 反射破坏了, 应对反射破坏单例的方式,我可以改造一下 SingleTest01 构造方法,改造后如下:
private SingleTest01(){
if (singleTest != null){
throw new RuntimeException("单例对象不允许调用构造方法!!");
}
System.out.println("SingleTest01 init ");
}
如果我们将一个对象 序列化 到文件 然后再从文件读入到内存中,会生成一个新的对象破坏单例吗, ok,我们以SingleTest03 【类必须要实现序列化, SingleTest03代码中已经实现】试一下:
import lombok.SneakyThrows;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestSerializable {
@SneakyThrows
public static void main(String[] args) {
SingleTest03 singleTest03 = SingleTest03.newSingleTest();
// 写入到文件
FileOutputStream outFile = new FileOutputStream("SingleTest03");
ObjectOutputStream outObject = new ObjectOutputStream(outFile);
outObject.writeObject(singleTest03);
// 从文件中读取对象
FileInputStream fileIn = new FileInputStream("SingleTest03");
ObjectInputStream objIn = new ObjectInputStream(fileIn);
SingleTest03 fileSingleTest01 = (SingleTest03) objIn.readObject();
System.out.println("singleTest03 是否等于 fileSingleTest01: " + (singleTest03 == fileSingleTest01));
}
}
执行结果, 如下, 单例模式再次被序列化与反序列化破坏
这… 单例模式再次被破坏,我们先看一下 java.io.Serializable, 注释里面有行代码:
意思: 反序列时执行序列化对象里面的这个方法获得新的对象,而不是从反序列化文件中获取
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
public class SingleTest03 implements Serializable {
private static final long serialVersionUID = 1L;
...........
Object readResolve() throws ObjectStreamException {
return SingleTest03.newSingleTest();
}
}
再次执行结果如下:
我们可以看到,及时是反序列化生成的对象不再是从文件中获取的, 而是从readResolve() 拿到的 【写到文件中“张三” 的对象没有加载出来】
为什么? 为什么 readResolve() 方法有如此大的 能力 ~
我们从代码一步步跟进去:
1、文件输入流开始读取对象
2、调用 ObjectInputStream 中 readObject0 构建对象
3、在 readObject0()中进入 readOrdinaryObject() 中
4、在 readOrdinaryObject() 中判断是否有 readResolve() 方法,并反射调用该方法,生成最终的对象。
枚举对于上述的 反射、序列化是如何处理,并保证真正的单例 ,可以看一下我的下一遍文章,关于枚举的讲解
java能力-枚举浅析: https://blog.csdn.net/zhangyong01245/article/details/119841606