栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

单例模式学习笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

单例模式学习笔记

单例模式

饿汉式单例模式:

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个线程创建单例,结果如下:

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

解决办法:加锁

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

        return dclLazyMan;
    }

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

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

指令重排

原因:创建对象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 declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

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

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

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....");
    }

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

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

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

解决办法:使用标识符

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....");
    }

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

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

        Field flag = DCLLazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);
        Constructor 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);
    }

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

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

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

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

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 declaredConstructor = enumSingle.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            enumSingle instance2 = declaredConstructor.newInstance();
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }

报错如下:

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

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

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

修改后:

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/732928.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号