您的位置:首页 > 编程语言 > Go语言

GOF23 单例模式

2021-03-21 15:25 771 查看

单例模式

优点

  1. 单例模式可以保证内存中只有一个实例,减少了内存的开销
  2. 可以避免对资源的多重占用
  3. 单例模式设置全局访问点,可以优化和共享资源的访问

缺点

  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
  2. 在并发测试中,单例模式不利于代码调试。在调试的过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责。

饿汉式单例

缺点

当存在大量的单例对象的时候,而且单例的数量不能确定,则系统初始化过程中会造成资源浪费,从而导致系统内存不足

// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];

private Hungry() {
}

private final static Hungry HUNGRY = new Hungry();

public static Hungry getInstance() {
return HUNGRY;
}
}

懒汉式单例 DCL

双重检查锁单例

public class LazyMan {
private volatile static LazyMan lazyMan; //禁止指令重排
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
public static LazyMan getInstance(){
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
if (lazyMan==null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}

public static void main(String[] args) {
//测试是否为单例对象
for (int i = 0; i <10 ; i++) {
new Thread(()->{
getInstance();
}).start();
}
}

}

通过反射初步破坏单例模式

public class LazyMan {
private volatile static LazyMan lazyMan; //禁止指令重排
private LazyMan(){
//加锁判断lazyMan是否存在 存在的话抛出异常
synchronized(LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要通过反射破坏");
}
}
}
public static LazyMan getInstance(){
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
if (lazyMan==null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan lazyMan = getInstance();
LazyMan lazyMan1 = constructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}

}

继续再次破坏单例模式

道高一尺 魔高

public class LazyMan {
private volatile static LazyMan lazyMan; //禁止指令重排
private volatile static boolean zjh=false; // 添加一个判断字段
private LazyMan(){
synchronized(LazyMan.class){
if (!zjh){
zjh=true;
}else{
throw new RuntimeException("不要试图通过反射破坏单例模式");
}
}
}
public static LazyMan getInstance(){
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
if (lazyMan==null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
Field zjh = LazyMan.class.getDeclaredField("zjh"); //获取zjh字段
constructor.setAccessible(true); //强制
//        LazyMan lazyMan = getInstance();
zjh.setAccessible(true);
LazyMan lazyMan1 = constructor.newInstance();
zjh.set(lazyMan1,false); //设置字段的值为false
LazyMan lazyMan = constructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}

}
/
**
* 1. 分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* *
123
* 132 A
* B // 此时lazyMan还没有完成构造
*/

静态内部类

public class Holder {
private Holder(){

}

public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
  • 单例不安全,可以通过反射破解

枚举

枚举式单例写法

public enum EnumSingleton implements Serializable {
INSTALL;
private Object data;

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

public static EnumSingleton getInstance(){
return INSTALL;
}
}
  • 测试
public class EnumSingletonTest {

public static void main(String[] args) {
try{
EnumSingleton instance1=null;
EnumSingleton instance2=EnumSingleton.getInstance();
instance2.setData(new Object());

FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeObject(instance2);
oos.flush();
oos.close();

FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj");
ObjectInputStream oos2 = new ObjectInputStream(fileInputStream);
instance1= (EnumSingleton) oos2.readObject();
oos2.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData()==instance2.getData());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
  • 结果

  • 这根我们预期的结果不太一样
// 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:   EnumSingleton.java
package 521B5EFA578B6A215F0F.53554F8B6A215F0F;
import java.io.Serializable;

public final class EnumSingleton extends Enum
implements Serializable
{

public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
//toString的逆方法,返回指定名字,给定类的枚举常量
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(521B5EFA578B6A215F0F/53554F8B6A215F0F/EnumSingleton, name);
}
//私有构造函数,参数有 此枚举常量的名称,枚举常量的序号
private EnumSingleton(String s, int i)
{
super(s, i);
}

public Object getData()
{
return data;
}

public void setData(Object data)
{
this.data = data;
}

public static EnumSingleton getInstance()
{
return INSTALL;
}
//单例对象的名称
public static final EnumSingleton INSTALL;
//单例对象的属性
private Object data;
//枚举类中的所有值 包装到values数组中
private static final EnumSingleton $VALUES[];
static
{
//与饿汉式相似,类初始化时创建单例对象  指定名称和枚举常量的序号
INSTALL = new EnumSingleton("INSTALL", 0);
$VALUES = (new EnumSingleton[] {
INSTALL
});
}
}

枚举式单例写法在静态块中就对INSTALL赋值了,是饿汉式的实现

  • 至此我们还可以想序列化是否可以破解枚举的单例 ,现在回到
    ObjectInputStream的readObject0()
    方法。

  • 接下来我们看
    readEnum
    的代码实现
/**
* Reads in and returns enum constant, or null if enum type is
* unresolvable.  Sets passHandle to enum constant's assigned handle.
*/
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}

String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}

​ 由上可知,枚举类型其实通过类名和类对象找到唯一的枚举对象。因此,枚举对象不可能被类加载器加载两次。

试图通过反射破坏枚举的单例模式

public enum EnumSingle {
INSTANCE;
private EnumSingle getInstance(){
return INSTANCE;
}

}

class Test01{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
}
}
  • 显示没有无参构造方法 而不是显示不能通过反射调用

再次通过反射破解

public enum EnumSingle {
INSTANCE;
private EnumSingle getInstance(){
return INSTANCE;
}
}

class Test01{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
}
}

查看

newInstance
源码

@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//判断是否为枚举类
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
//如果是直接抛出异常
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor;   // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

反编译源码

// 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:   EnumSingle.java

package com.itheim.Demo14;

public final class EnumSingle extends Enum
{

public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}

public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/itheim/Demo14/EnumSingle, name);
}

private EnumSingle(String s, int i)
{
super(s, i);
}

public EnumSingle getInstance()
{
return INSTANCE;
}

public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];

static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}

还原反序列化破坏单例模式的事故现场

  • 序列化就是把内存中的状态通过转换为字节码的形式 从而转换为IO流,写入其他的地方(可以是磁盘、网络IO)内存中的状态会被永久保留下来
  • 反序列化就是把已经序列化的字节码内容转换为IO流 通过IO流的读取,进而将读取的内容转换为
    java
    对象在转换过程中重新创建对象
package 创建型模式.单例模式;

import java.io.*;

/**
* @program: DesignPattern
* @description:
* @author: ZGrey
* @create: 2021-03-21 13:53
**/
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE=new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
public static void main(String[] args) {
try{
SeriableSingleton instance1=null;
SeriableSingleton instance2=SeriableSingleton.getInstance();

FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeObject(instance2);
oos.flush();
oos.close();

FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj");
ObjectInputStream oos2 = new ObjectInputStream(fileInputStream);
instance1= (SeriableSingleton) oos2.readObject();
oos2.close();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1==instance2);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
  • 结果如图所示 发现并不是同一个对象实例

  • 那么如何通过序列化实现单例模式呢 其实很简单 添加
    readResolve
    方法即可
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE=new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
  • 再次运行:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: