前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式 - 单例模式

设计模式 - 单例模式

作者头像
断痕
发布2021-01-21 14:53:52
3940
发布2021-01-21 14:53:52
举报
文章被收录于专栏:edlcloud

饿汉式

代码语言:javascript
复制
//饿汉式
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;
    }
}

懒汉式

代码语言:javascript
复制
//懒汉式
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private static LazyMan LazyMan;

    public static LazyMan getInstance(){

        if (LazyMan == null){
            LazyMan = new LazyMan();
        }
        return LazyMan;
    }
	//单线程OK
	//多线程并发不OK,起10个线程看一下
    public static void main(String[] ages){
        for (int i = 0; i < 10; i++){
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}
image-20200817223813459
image-20200817223813459

执行后会发现他已经乱掉了,之后我们加一把锁,再跑一下。

代码语言:javascript
复制
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private static LazyMan LazyMan;
    //双重检测锁模式的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        if (LazyMan == null){
            //类加锁 synchronized
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }
    public static void main(String[] ages){
        for (int i = 0; i < 10; i++){
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}
image-20200817224330072
image-20200817224330072

不管怎么跑都是一个,但是 LazyMan = new LazyMan();在极端情况下是有问题的,因为他不是一个原子性操作

代码语言:javascript
复制
        if (LazyMan == null){
            //类加锁 synchronized
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 123 理想化
                     * 132 A
                     *     B 此时LazyMan还未完成构造
                     */
                }
            }
        }
        return LazyMan;
    }

他会经历以下步骤

  • 1.他会分配内存空间
  • 2.执行构造方法,初始化对象
  • 3.把这个对象指向这个空间

这个时候他可能会发生指令重排现象比如,我期望他执行123操作,但他真实可能执行132操作

比如走向了132 A线程他是没有问题的,但是突然来了一条B线程,线程B由于他已经把对象指向这个空间了,他会认为LazyMan 不等于 null 会直接return LazyMan,但此时 LazyMan还没有完成构造,他还是空的,所以可能会发生指令重排。为了防止这种现象。可以使用volatile避免进行指令重排。

这就是完整的双重检测锁+原子性操作

代码语言:javascript
复制
//懒汉式
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    //避免指令重排 volatile
    private volatile static LazyMan LazyMan;
    //双重检测锁模式的 懒汉式单例 DCL懒汉式
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            //类加锁 synchronized
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }
    public static void main(String[] ages){
        for (int i = 0; i < 10; i++){
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

静态内部类

代码语言:javascript
复制
//静态内部类
public class Holder {

    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.Holder;
    }
    public static class InnerClass{
        private static final Holder Holder = new Holder();
    }
}

它可以加载外部类不会加载内部类,这样可以实现懒加载,节省资源。但是他是不安全的。因为java有反射机制。

反射

首先获得第一个对象

代码语言:javascript
复制
import java.lang.reflect.Constructor;

//懒汉式
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }

    //反射
	public static void main(String[] ages) throws Exception {
        LazyMan instance = LazyMan.getInstance();//获得第一个对象
    	LazyMan instance2 = LazyMan.getInstance();
    }
}

正常情况下上面两个对象应该是相等的。但是如果用反射去破坏这个单例呢。

代码语言:javascript
复制
import java.lang.reflect.Constructor;

//懒汉式
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }

    //反射
    public static void main(String[] ages) throws Exception {
        LazyMan instance = LazyMan.getInstance();//获得第一个对象
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器 抛出异常
        declaredConstructor.setAccessible(true);//暴力反射 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建一个对象

        System.out.println(instance);
        System.out.println(instance2);

    }
}
image-20200817233656365
image-20200817233656365

按照单例来说这两个值应该相等,所以得出结论

  • 反射可以破坏单例

当然也可以去解决这个问题,代码中无参走的是 private LazyMan(){},在这里面加一把锁

代码语言:javascript
复制
package designModel.Singleton;

import java.lang.reflect.Constructor;

//懒汉式
public class LazyMan {

    private LazyMan(){
        //类加锁
        synchronized (LazyMan.class){
            if(LazyMan != null){
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }

    //反射
    public static void main(String[] ages) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);

    }
}
image-20200817234636430
image-20200817234636430

这种反射就破坏失败了,但是还有问题,上面代码已经把双重检测变成了三重检测,但是第一个线程默认使用的

代码语言:javascript
复制
LazyMan instance = LazyMan.getInstance();

.getInstance去得到的,但当我两个对象都是使用

代码语言:javascript
复制
 LazyMan instance = declaredConstructor.newInstance();

执行

代码语言:javascript
复制
import java.lang.reflect.Constructor;

//懒汉式
public class LazyMan {

    private LazyMan(){
        synchronized (LazyMan.class){
            if(LazyMan != null){
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }

    //反射
    public static void main(String[] ages) throws Exception {
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);

        LazyMan instance = declaredConstructor.newInstance(); //通过反射创建对象
        LazyMan instance2 = declaredConstructor.newInstance(); //通过反射创建对象

        System.out.println(instance); 
        System.out.println(instance2);
    }
}
image-20200817235334078
image-20200817235334078

然而单例又被破坏了。当然这也可以解决。

首先定义一个变量nica啥都行也可以传密钥。

代码语言:javascript
复制
import java.lang.reflect.Constructor;

//懒汉式
public class LazyMan {

    //创建一个标志位
    private static boolean nicai = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            //判断
            if(nicai == false){
                nicai = true;
            }else {
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }
    public static void main(String[] ages) throws Exception {
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance(); 
        LazyMan instance2 = declaredConstructor.newInstance(); 
        System.out.println(instance); 
        System.out.println(instance2);
    }
image-20200818000805148
image-20200818000805148

第一次执行无论执不执行反射判断都会变,除非反编译反射是找不到定义的变量的,再加一些加密处理会更安全。

但是,定义的nicai这个变量不能够被破坏吗,加密也是能够解密的。

假如我找到了nicai这个变量。

代码语言:javascript
复制
import javax.swing.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//懒汉式
public class LazyMan {

    private static boolean nicai = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if(nicai == false){
                nicai = true;
            }else {
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }
    private volatile static LazyMan LazyMan;
    public  static LazyMan getInstance(){
        if (LazyMan == null){
            synchronized (LazyMan.class){
                if (LazyMan == null){
                    LazyMan = new LazyMan();
                }
            }
        }
        return LazyMan;
    }

    //反射
    public static void main(String[] ages) throws Exception {
        //找到变量
        Field nicai = LazyMan.class.getDeclaredField("nicai");
        nicai.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        
        LazyMan instance = declaredConstructor.newInstance(); 
		//赋值
        nicai.set(instance,false);

        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance); 
        System.out.println(instance2);

    }
}
image-20200818004818977
image-20200818004818977

单例又被破坏了,得出结论

  • 理解到这步有点儿难。道高一尺魔高一丈。

接下来看源码寻找答案。

查看newInstance源码

代码语言:javascript
复制
public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, modifiers);
        }
        //如果这个类型是一个枚举类型会抛出 Cannot reflectively create enum objects(无法以反射方式创建枚举对象)
        //枚举来源jdk1.5它自带单例模式
         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;
    }

枚举

枚举是什么? 本身也是一个class类

代码语言:javascript
复制
public enum  EnumSingle {

        INSTANCE;

        public EnumSingle getInstance(){
            return INSTANCE;
        }
}

class Test{
    public static void main(String[] args){

       EnumSingle instance =  EnumSingle.INSTANCE;
       EnumSingle instance2 =  EnumSingle.INSTANCE;

       System.out.println(instance);
       System.out.println(instance2);

    }
}
image-20200818010424704
image-20200818010424704

试一下用反射破坏枚举

首先分析源码看一下枚举是有参还是有参构造。

image-20200818011139794
image-20200818011139794

空参的,然后用反射去破坏枚举

代码语言:javascript
复制
import java.lang.reflect.Constructor;

public enum  EnumSingle {

        INSTANCE;

        public EnumSingle getInstance(){
            return INSTANCE;
        }
}

class Test{
    public static void main(String[] args) throws Exception {
       EnumSingle instance =  EnumSingle.INSTANCE;
       Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
       declaredConstructor.setAccessible(true);
       EnumSingle instance2 = declaredConstructor.newInstance();
       
       System.out.println(instance);
       System.out.println(instance2);
    }
}
image-20200818011633721
image-20200818011633721

但是这个结果不太对,结果写的是NoSuchMethodException: design.Singleton.EnumSingle(类中没有空参构造器),但是正常看枚举源码报的错应该是Cannot reflectively create enum objects(无法以反射方式创建枚举对象)。完蛋。对不上啊。继续研究。

首先IDEA看源码上面写了,有个空参构造方法,但是跑过之后发现里面没有这个东西。

image-20200818012129905
image-20200818012129905

IDEA已经看不出来了

通过源码反编译代码去寻找问题

image-20200818012359446
image-20200818012359446
image-20200818012414374
image-20200818012414374

找到源代码文件夹。

CMD模式启动,反编译EnumSingle.class

代码语言:javascript
复制
javap -p -[类名]		# 对javac编译的文件进行反编译
image-20200818012648341
image-20200818012648341

首先会看到这个这个枚举本身也是一个Class只是继承了枚举的类

代码语言:javascript
复制
public final class design.Singleton.EnumSingle extends java.lang.Enum<design.Singleton.EnumSingle> //继承了枚举的类
{
  public static final design.Singleton.EnumSingle INSTANCE;
  private static final design.Singleton.EnumSingle[] $VALUES;
  public static design.Singleton.EnumSingle[] values();
  public static design.Singleton.EnumSingle valueOf(java.lang.String);
  private design.Singleton.EnumSingle();//有一个空参的构造
  public design.Singleton.EnumSingle getInstance();
  static {};
}

但是反编译之后发现也是有一个空参的构造和IDEA看的一样。

换一个更专业的软件去反编译再看看

什么是jad

下载jad.exe

下载好的jad.exe放到源码目录下

image-20200818013942137
image-20200818013942137

指定命令将源码.Class变成一个java文件

代码语言:javascript
复制
jad -sjava [.class]
image-20200818014123443
image-20200818014123443
image-20200818014149661
image-20200818014149661

打开生成的EnumSingle.java

代码语言:javascript
复制
// 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 design.Singleton;


public final class EnumSingle extends Enum //还是继承
{

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

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(design/Singleton/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
        });
    }
}

还是继承了枚举类,但是上面的代码中存在有参构造器,一个String,一个int。

回到IDEA编写反射枚举代码

代码语言:javascript
复制
import java.lang.reflect.Constructor;

public enum  EnumSingle {

        INSTANCE;

        public EnumSingle getInstance(){
            return INSTANCE;
        }
}

class Test{
    public static void main(String[] args) throws Exception {
       EnumSingle instance =  EnumSingle.INSTANCE;
       Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//更改了类型
       declaredConstructor.setAccessible(true);
       EnumSingle instance2 = declaredConstructor.newInstance();

       System.out.println(instance);
       System.out.println(instance2);
    }
}
image-20200818014649325
image-20200818014649325

这下是对了。绝了 。结论

  • 使用枚举不能破坏单例

代码开源上面的例子都在下面的连接里用Git直接拉就行

代码语言:javascript
复制
https://gitee.com/DH_2/DesignPatterns.git
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 饿汉式
  • 懒汉式
  • 静态内部类
  • 反射
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档