你写的单例模式,真正安全吗?
Posted 申码er
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你写的单例模式,真正安全吗?相关的知识,希望对你有一定的参考价值。
作者:Sicimike 链接:blog.csdn.net/Baisitao_/article/details/104452776
-
构造方法私有 -
提供一个静态方法,使得外部通过该方法获取单例类的实例
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。
饿汉式单例是指在单例类加载的时候就初始化一个对象,不管之后的程序会不会用到。因为显得迫不及待,感觉很饿,所以叫饿汉式单例。
懒汉式单例类似懒加载,只有程序第一次用到的时候,才开始实例化,所以叫懒汉式单例。
饿汉式单例
/**
* 恶汉式单例,线程安全
* @author sicimike
* @create 2020-02-23 20:15
*/
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
/**
* 饿汉式单例,线程安全
* @author sicimike
* @create 2020-02-23 20:19
*/
public class Singleton2 {
private static Singleton2 INSTANCE = null;
static {
INSTANCE = new Singleton2();
} private Singleton2() {}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
懒汉式单例
/**
* 懒汉式单例,线程不安全
* @author sicimike
* @create 2020-02-23 20:21
*/
public class Singleton3 {
private static Singleton3 INSTANCE = null;
private Singleton3() {}
public static Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
/**
* 懒汉式单例,线程安全
* synchronized关键字实现
* @author sicimike
* @create 2020-02-23 20:26
*/
public class Singleton4 {
private static Singleton4 INSTANCE = null;
private Singleton4() {}
public static synchronized Singleton4 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
return INSTANCE;
}
}
/**
* 懒汉式单例,线程不安全
* @author sicimike
* @create 2020-02-23 20:29
*/
public class Singleton5 {
private static Singleton5 INSTANCE = null;
private Singleton5() {}
public static Singleton5 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton5.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton5();
}
}
}
return INSTANCE;
}
}
这其实是并发编程中常见的一种手段,叫double-check,也就是常说的双重校验(博主的并发编程分类,写过一些JDK并发容器、线程池的源码,很多地方用到了双重校验,有兴趣的同学可以去翻一翻)。假设线程A执行到了第15行(尚未执行)if (INSTANCE == null),此时INSTANCE依然等于null,而线程B可能已经进入了外层判断,而被阻塞在synchronized这里。线程A继续执行完成对象的创建后释放锁,线程B获取锁进入同步代码块,如果没有第二次判断,线程B会直接创建对象。所以synchronized内也必须加一次判断。
1、分配内存给这个对象
2、初始化这个对象
3、把INSTANCE变量指向初始化的对象
正常情况下按照1 -> 2 -> 3的顺序执行,但是2和3可能会发生重排序,执行顺序变成1 -> 3 -> 2。如果是1 -> 3 -> 2的顺序执行。线程A执行完3,此时对象尚未初始化,但是INSTANCE变量已经不为null,线程B执行到synchronized关键字外部的if判断时,就直接返回了。此时线程B拿到的是一个尚未初始化完成的对象,可能会造成安全隐患。所以这种实现方式是线程不安全的。要向解决这个问题,也就是解决重排序的问题,聪明的你应该想到了另一个关键字volatile,再次改造下代码
/**
* 懒汉式单例,线程安全
* 双重校验锁
* @author sicimike
* @create 2020-02-23 20:34
*/
public class Singleton6 {
private static volatile Singleton6 INSTANCE = null;
private Singleton6() {}
public static Singleton6 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton6.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton6();
}
}
}
return INSTANCE;
}
}
-
解决了重排序的问题 -
保证了INSTANCE的修改,能够及时的被其他线程所知
/**
* 懒汉式单例,线程安全
* 静态内部类
* @author sicimike
* @create 2020-02-23 20:36
*/
public class Singleton7 {
private Singleton7() {}
public static Singleton7 getInstance() {
return InnerClass.INSTANCE;
}
private static class InnerClass {
private static Singleton7 INSTANCE = new Singleton7();
}
}这种实现方式既满足懒加载,又满足线程安全,代码量还少,相对来说是一种比较优雅的实现方式。
单例与序列化
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。
/**
* 单例模式与序列化
* @author sicimike
* @create 2020-02-23 22:26
*/
public class SingletonWithSerialize implements Serializable {
private static final long serialVersionUID = 6133201454552796162L;
private static final SingletonWithSerialize INSTANCE = new SingletonWithSerialize();
private SingletonWithSerialize() {}
public static SingletonWithSerialize getInstance() {
return INSTANCE;
}
}
/**
* @author sicimike
* @create 2020-02-23 20:48
*/
public class SingletonDemo {
private static final String FILE_PATH = "singleton.data";
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingletonWithSerialize instance = SingletonWithSerialize.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(FILE_PATH)));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(FILE_PATH)));
SingletonWithSerialize readInstance = (SingletonWithSerialize) ois.readObject();
System.out.println(instance);
System.out.println(readInstance); // 关闭IO流
}
}
com.sicimike.creation.singleton.SingletonWithSerialize@7f31245a
com.sicimike.creation.singleton.SingletonWithSerialize@7cca494b
The readResolve method is called when ObjectInputStream has read an object from the stream and is preparing to return it to the caller. ObjectInputStream checks whether the class of the object defines the readResolve method. If the method is defined, the readResolve method is called to allow the object in the stream to designate the object to be returned.
——https://docs.oracle.com/javase/8/docs/platform/serialization/spec/input.html#a5903
/**
* 单例模式与序列化
* @author sicimike
* @create 2020-02-23 22:26
*/
public class SingletonWithSerialize implements Serializable {
private static final long serialVersionUID = 6133201454552796162L;
private static final SingletonWithSerialize INSTANCE = new SingletonWithSerialize();
private SingletonWithSerialize() {}
public static SingletonWithSerialize getInstance() {
return INSTANCE;
} // 解决序列化与反序列化破坏单例模式的问题
private Object readResolve() {
return this.INSTANCE;
}
}
com.sicimike.creation.singleton.SingletonWithSerialize@6d6f6e28
com.sicimike.creation.singleton.SingletonWithSerialize@6d6f6e28
单例与反射
/**
* @author sicimike
* @create 2020-02-23 20:48
*/
public class SingletonDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton1> classObject = Singleton1.class;
Constructor<Singleton1> constructor = classObject.getDeclaredConstructor();
constructor.setAccessible(true);
// 单例模式获取
Singleton1 instance = Singleton1.getInstance();
// 反射获取
Singleton1 reflectInstance = constructor.newInstance();
System.out.println(instance);
System.out.println(reflectInstance);
}
}
com.sicimike.creation.singleton.Singleton1@4554617c
com.sicimike.creation.singleton.Singleton1@74a14482
第八种单例
/**
* 枚举实现单例,线程安全
* @author sicimike
* @create 2020-02-23 20:46
*/
public enum Singleton8 {
INSTANCE;
public static Singleton8 getInstance() {
return INSTANCE;
}
}再来测试下反射
/**
* @author sicimike
* @create 2020-02-23 20:48
*/
public class SingletonDemo {
private static final String FILE_PATH = "singleton.data";
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton8> classObject = Singleton8.class;
Constructor<Singleton8> constructor = classObject.getDeclaredConstructor();
constructor.setAccessible(true);
// 单例模式获取
Singleton8 instance = Singleton8.getInstance();
// 反射获取
Singleton8 reflectInstance = constructor.newInstance();
System.out.println(instance);
System.out.println(reflectInstance);
}
}执行会得到如下结果
Exception in thread "main" java.lang.NoSuchMethodException: com.sicimike.creation.singleton.Singleton8.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.sicimike.creation.singleton.SingletonDemo.main(SingletonDemo.java:31)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Singleton8.java
package com.sicimike.creation.singleton;
public final class Singleton8 extends Enum
{
public static Singleton8[] values()
{
return (Singleton8[])$VALUES.clone();
}
public static Singleton8 valueOf(String name)
{
return (Singleton8)Enum.valueOf(com/sicimike/creation/singleton/Singleton8, name);
}
private Singleton8(String s, int i)
{
super(s, i);
}
public static Singleton8 getInstance()
{
return INSTANCE;
}
public static final Singleton8 INSTANCE;
private static final Singleton8 $VALUES[];
static
{
INSTANCE = new Singleton8("INSTANCE", 0);
$VALUES = (new Singleton8[] {
INSTANCE
});
}
}
-
默认继承了java.lang.Enum类 -
生成了一个私有的构造方法Singleton8(String s, int i),并不是无参的 -
INSTANCE实例在静态代码块中被创建
/**
* @author sicimike
* @create 2020-02-23 20:48
*/
public class SingletonDemo {
private static final String FILE_PATH = "singleton.data";
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton8> classObject = Singleton8.class;
Constructor<Singleton8> constructor = classObject.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
// 单例模式获取
Singleton8 instance = Singleton8.getInstance();
// 反射获取
Singleton8 reflectInstance = constructor.newInstance("Sicimike", 18);
System.out.println(instance);
System.out.println(reflectInstance);
}
}执行结果如下
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.sicimike.creation.singleton.SingletonDemo.main(SingletonDemo.java:59)
以上是关于你写的单例模式,真正安全吗?的主要内容,如果未能解决你的问题,请参考以下文章