饥汉模式

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

image-20230314155639972

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

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

image-20230314160149602

但依然存在问题, 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);
    }
}

image-20230314163414487

至此才得以真正解决

JUC
作者:|卓亦苇|,原文链接: https://www.cnblogs.com/zhuoblog/p/17215471.html

文章推荐

一分钟学一个 Linux 命令 - find 和 grep

APP中RN页面渲染流程-ReactNative源码分析

mysql基础_视图

SpringBoot 使用 Sa-Token 完成权限认证

[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范

最新版本 Stable Diffusion 开源 AI 绘画工具之使用篇

深入理解 python 虚拟机:字节码灵魂——Code obejct

nginx各种代理配置

windows 系统下 workerman 在同一个运行窗口中开启多个 webs...

SparkSteaming写elasticsearch问题

《回炉重造 Java 基础》——集合(容器)

关于TornadoFx和Android的全局配置工具类封装实现及思路解析