单例模式学习笔记

5 篇文章 0 订阅

单例模式

饿汉式单例模式:

package com.zyc.playSingle;


//饿汉式单例模式
public class hungryMan {
    //私有构造器,这样就不能通过构造器创建实例了
    private hungryMan(){

    }

    private static final hungryMan HUNGR_MAN = new hungryMan();

    //只要有就给:饿汉的特点,使用static这样可以直接用类访问
    public static hungryMan getHungrMan(){
        return HUNGR_MAN;
    }
}

class t1{
    public static void main(String[] args) {
        hungryMan hungrMan = com.zyc.playSingle.hungryMan.getHungrMan();
        hungryMan hungrMan1 = hungryMan.getHungrMan();
        hungryMan hungrMan2 = hungryMan.getHungrMan();

        System.out.println(hungrMan);
        System.out.println(hungrMan1);
        System.out.println(hungrMan2);
    }
}

懒汉式单例模式:

package com.zyc.playSingle;


//懒汉式单例模式
public class lazyMan {

    private lazyMan(){

    }

    private  static lazyMan LAZY_MAN ;


    public static lazyMan getLAZY_MAN(){
        if (LAZY_MAN==null){
            LAZY_MAN= new lazyMan();
            return LAZY_MAN;
        }
        return LAZY_MAN;
    }

}
class t2{
    public static void main(String[] args) {
        lazyMan lazy_man = lazyMan.getLAZY_MAN();
        lazyMan lazy_man1 = lazyMan.getLAZY_MAN();
        System.out.println(lazy_man);
        System.out.println(lazy_man1);
    }
}

单线程情况下我们使用单例模式是安全的,那么多线程呢?

package com.zyc.playSingle;

public class DCLLazyMan {

    private  DCLLazyMan(){
      System.out.println(Thread.currentThread().getName()+"ing...."); //测试 每次打印创建单例的线程名
    }

    private static DCLLazyMan dclLazyMan;
	//懒汉模式
    public static DCLLazyMan getDclLazyMan(){
        if(dclLazyMan==null){
            dclLazyMan = new DCLLazyMan();
        }
        return dclLazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{

                DCLLazyMan.getDclLazyMan();

            }).start();
        }
    }
}

这里我们使用10个线程创建单例,结果如下:

image-20220207180605412

image-20220207180617899

image-20220207180627601

我们看到结果不同,所以多线程情况下这个是线程不安全

解决办法:加锁

//双重检测锁模式 DCL懒汉式
    public static DCLLazyMan getDclLazyMan(){
        if (dclLazyMan==null){
            synchronized (DCLLazyMan.class){
                if(dclLazyMan==null){
                    dclLazyMan = new DCLLazyMan();
                }
            }
        }

        return dclLazyMan;
    }

使用双重检测锁我们看到结果如下:
image-20220207182106292

image-20220207182118176

结果现在满足了我们的需求,那么已经完全保证线程问题了吗?

指令重排

原因:创建对象dclLazyMan = new DCLLazyMan();这行代码不是一个原子性操作,可能会造成指令重排(1.分配内存空间2.初始化对象3.对象指向内存空间//这三步的顺序会重新排序,有可能先进行3操作在进行2)

所以我们需要使用volatile避免指令重排

更改后代码如下:

package com.zyc.playSingle;

public class DCLLazyMan {

    private  DCLLazyMan(){
        System.out.println(Thread.currentThread().getName()+"ing....");
    }

    private volatile static DCLLazyMan dclLazyMan;

    //双重检测锁模式 DCL懒汉式
    public static DCLLazyMan getDclLazyMan(){
        if (dclLazyMan==null){
            synchronized (DCLLazyMan.class){
                if(dclLazyMan==null){
                    dclLazyMan = new DCLLazyMan();
                }
            }
        }

        return dclLazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{

                DCLLazyMan.getDclLazyMan();

            }).start();
        }
    }
}

使用反射破解单例模式

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        DCLLazyMan dclLazyMan1 = DCLLazyMan.getDclLazyMan();
        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207183514212

使用反射后可以发现创建两个对象不同;

解决办法,构造器进行判断

private static boolean flag  = false;

    private  DCLLazyMan(){
        synchronized (DCLLazyMan.class){
            if (flag==false){
                flag=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }

        System.out.println(Thread.currentThread().getName()+"ing....");
    }

image-20220207184139154

但是我们上面是用反射创建了一个对象和原本创建的对象进行比较,如果全部使用反射创建对象呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
        DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207184432521

解决办法:使用标识符

private static boolean flag  = false;

    private  DCLLazyMan(){
        synchronized (DCLLazyMan.class){
            if (flag==false){
                flag=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }

        System.out.println(Thread.currentThread().getName()+"ing....");
    }

image-20220207184837259

但是如果通过反编译或者其他手段获得我们设置的标识符呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        Field flag = DCLLazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);
        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();

        flag.set(dclLazyMan,false);
        DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207185224041

通过修改标识符我们单例模式又被破坏了

解决办法:通过源码分析使用枚举

image-20220207185432060

我们下面使用枚举类型进行分析

创建枚举类然后利用反射创建对象:

package com.zyc.playSingle;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

    //enum 是什么? enum本身就是一个Class 类
    public enum enumSingle {
        INSTANCE;
        public enumSingle getInstance(){
            return INSTANCE;
        }
    }

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

报错如下:

image-20220207195746059

我们发现结果跟源码中读的不同:

经过JAD工具进行反编译我们可以得到这里不是一个空参构造器,而是一个有参构造器

Constructor<enumSingle> declaredConstructor = enumSingle.class.getDeclaredConstructor(String.class,int.class);

修改后:image-20220207200046413

抛出异常正确,成功使用枚举类避免了反射的破坏

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者



你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值