7

深度解析单例模式 - 卓亦苇

 1 year ago
source link: https://www.cnblogs.com/zhuoblog/p/17215471.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

深度解析单例模式 - 卓亦苇 - 博客园

package com.cz.single;

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/11 21:31
 */
public class Hungry {

    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];
    private Hungry(){

    }

    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }

    public static void main(String[] args) {
     .   Hungry.getInstance();
    }
}

会浪费内存,执行代码,其4个对象已经被创建,浪费空间。

懒汉式单例

package com.cz.single;

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/11 21:35
 */
public class LazyMan {

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

    public static LazyMan getLazyMan(){
        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(()->{
                LazyMan.getLazyMan();
            }).start();
        }
    }
}

懒汉式为使用该对象才创建新对象,但是初始代码有问题,单线程初始没有问题,多线程会造成,非单例。

解决办法,首先加锁,先判断对象是否为空,如果为空则将class对象进行上锁,然后需再判断,锁是否为空,如果为空再创建新对象。

同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。第二层,是因为使用同步代码块才加上的,有的可能过了第一个if,没到同步代码块

为双层检测的懒汉式单例,也称DCL懒汉式

第二个问题

lazyMan = new LazyMan();

代码为非原子性操作

创建新对象的底层操作分为3步

1.分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
但如果不是原子操作,那132的状况式可能发现的,如果在A还没完成构造是,线程B进来,则不会执行if语句,发生错误

让lazyMan加上volatile参数

反射会破坏单例模式

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

//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                LazyMan.getLazyMan();
//            }).start();
//        }
        LazyMan lazyMan1 = LazyMan.getLazyMan();
        //获得空参构造器
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //反射的setAccessible(true)参数,无视私有构造器
        declaredConstructor.setAccessible(true);
        //通过反射建造对象
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

2689754-20230314155640421-1548464583.png

解决办法,在无参构造器添加异常

private LazyMan(){
    synchronized (LazyMan.class){
        if (lazyMan!=null){
            throw new RuntimeException("不要试图反射破坏");
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

2689754-20230314160149897-1349004271.png

但依然存在问题, if (lazyMan!=null)为非原子性操作,依然存在两个反射对象导致出现非单例的状况

//可以采用红绿灯解决,定义一个密钥
private static boolean key = false;
private LazyMan(){
    synchronized (LazyMan.class){
        if (key==false){
            key=true;
        }else {

                throw new RuntimeException("不要试图反射破坏");
             
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

但是如果仍然用反射破环,假设获取到了密钥的情况

//通过反射修改静态参数
Field key1 = LazyMan.class.getDeclaredField("key");
key1.setAccessible(true);

//获得空参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//反射的setAccessible(true)参数,无视私有构造器
declaredConstructor.setAccessible(true);
//通过反射建造对象
LazyMan lazyMan1 = declaredConstructor.newInstance();

//修改对象的密钥参数
key1.set(lazyMan1,false);

LazyMan lazyMan2 = declaredConstructor.newInstance();

System.out.println(lazyMan1);
System.out.println(lazyMan2);

单例仍然会被破坏

真实有效的方式枚举

package com.cz.single;

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

/**
 * @author 卓亦苇
 * @version 1.0
 * 2023/3/14 16:26
 */
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //EnumSingle instance2 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

2689754-20230314163414775-54352319.png

至此才得以真正解决

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK