JUC - 多线程之 单例模式(八)

news2024/11/20 3:26:59

单例模式(Singleton Pattern)是一种非常简单的设计模式之一,当我们使用的对象要在全局唯一时就需要用到该模式,以保证对象的唯一性。除此之外,还能避免反复的实例化对象,减少内存开销

单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

单例主要有如下创建方式

一、饿汉式--静态变量(线程安全

/**
 * 1、饿汉式-静态变量(线程安全)
 */
public class Hungry1 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry1(){
    }

    // 2、内部创建对象
    private static Hungry1 singleton = new Hungry1();

    // 3、对外获取实例的方法
    public static Hungry1 getInstance(){
        return singleton;
    }
}

优点:写法简单;避免了线程同步问题

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费 

二、饿汉式--静态代码块(线程安全

/**
 * 2、饿汉式-静态代码块(线程安全)
 */
public class Hungry2 {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];

    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Hungry2(){
    }

    // 2、内部静态代码块中创建对象
    private static Hungry2 singleton;
    static{
        singleton = new Hungry2();
    }

    // 3、对外获取实例的方法
    public static Hungry2 getInstance(){
        return singleton;
    }
}

优缺点同上 

三、懒汉式--简单判断非空(多线程并发不安全,单线程无影响)

/**
 * 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)
 */
public class Lazy1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy1() {}

    // 2、声明类成员变量singleton
    private static Lazy1 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy1 getInstance(){
        if (singleton == null){
            singleton = new Lazy1();
        }
        return singleton;
    }
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全,只适用单线程,当有多个线程访问时,能够产生多个对象,不满足单例模式的要求

在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

四、懒汉式--synchronized锁方法(线程安全,效率低)

/**
 * 4、懒汉式-synchronized锁方法(线程安全,效率低)
 */
public class Lazy2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy2() {}

    // 2、声明类成员变量singleton
    private static Lazy2 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy2 getInstance(){
        if (singleton == null){
            singleton = new Lazy2();
        }
        return singleton;
    }
}

优点:线程安全

缺点:效率太低synchronized锁了整个方法,下一个线程必须等上一个线程释放锁,效率很低,不建议使用

五、懒汉式--synchronized同步代码块(多线程并发不安全)

/**
 * 5、懒汉式-synchronized同步代码块(多线程并发不安全)
 */
public class Lazy3 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy3() {}

    // 2、声明类成员变量singleton
    private static Lazy3 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static synchronized Lazy3 getInstance(){
        if (singleton == null){
            synchronized (Lazy3.class){
                singleton = new Lazy3();
            }
        }
        return singleton;
    }
}

优点:在使用时才会生成对象,能够减少内存开销

缺点:线程不安全

假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

六、懒汉式--双重检查DCL(线程安全;常用)

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 */
public class Lazy4 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4() {}

    // 2、声明类成员变量singleton
    private volatile static Lazy4 singleton;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4 getInstance(){
        if (singleton == null){
            synchronized (Lazy4.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰
                     */
                    singleton = new Lazy4();
                }
            }
        }
        return singleton;
    }
}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象 

优点:效率高,线程安全

缺点:可通过反射破坏单例模式 

反射破坏双重懒汉式单例

1、反射破坏双重懒汉式单例--破坏私有无参构造器

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 1、反射破坏双重懒汉式单例--破坏私有无参构造器
 */
public class Lazy4Reflect {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect() {}

    // 2、声明类成员变量singleton
    private static Lazy4Reflect singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect();
                }
            }
        }
        return singleton;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        Lazy4Reflect instance = Lazy4Reflect.getInstance();
        Constructor<Lazy4Reflect> constructor = Lazy4Reflect.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        Lazy4Reflect instance1 = constructor.newInstance();

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

 输出如下,两个 instance内存地址明显不同

2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏
 */
public class Lazy4Reflect1 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect1() {
        synchronized (Lazy4Reflect1.class){
            if (singleton != null){
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect1 singleton;
    //private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect1 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect1.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect1();
                }
            }
        }
        return singleton;
    }
}

class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect1> constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect1 instance = constructor.newInstance();
        Lazy4Reflect1 instance1 = constructor.newInstance();

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

3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏

/**
 * 6、懒汉式-双重检查(线程安全;常用)
 *
 * 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏
 */
public class Lazy4Reflect2 {
    // 1、将构造方法私有化,外部无法使用new构造方法创建实例
    private Lazy4Reflect2() {
        synchronized (Lazy4Reflect2.class){
            if (isReflect = false){
                isReflect = true;
            }else {
                throw new RuntimeException("不要使用反射破坏异常");
            }
        }
    }

    // 2、声明类成员变量singleton
    private static Lazy4Reflect2 singleton;
    private static boolean isReflect = false;

    // 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
    public static Lazy4Reflect2 getInstance(){
        if (singleton == null){
            synchronized (Lazy4Reflect2.class){
                if (singleton == null){
                    /* 有可能 非原子性操作
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     * 123
                     * 132 ;此时lazyMan还没有完成构造,报出空指针异常
                     */
                    singleton = new Lazy4Reflect2();
                }
            }
        }
        return singleton;
    }
}

class Test2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 反射破坏单例
        Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");
        isReflectField.setAccessible(true);

        //Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
        Constructor<Lazy4Reflect2> constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // 两个对象都不经过 getInstance()创建,反射拿到
        Lazy4Reflect2 instance = constructor.newInstance();
        isReflectField.set(instance,false);
        Lazy4Reflect2 instance1 = constructor.newInstance();

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

七、静态内部类(线程安全;常用)

/**
 * 7、静态内部类(线程安全;常用)
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}

这种方式跟饿汉式方式采用的机制类似,但又有不同。

两者都是采用了类装载的机制来保证初始化实例时只有一个线程。

不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的 

优点:线程安全,延迟加载,效率高

缺点: 可通过反射破坏单例模式 

反射破坏

/**
 * 7、静态内部类(线程安全;常用)
 * 
 * 可以通过反射破坏
 */
public class StaticInnerClass {
    // 1、将构造方法私有化,外部无法使用new
    private StaticInnerClass(){

    }

    // 2、一个静态内部类 创建实例
    private static class StaticInstance{
        private static final StaticInnerClass INSTENCE  = new StaticInnerClass();
    }

    // 3、直接调用静态内部类,返回instance
    public static StaticInnerClass getInstance(){
        return StaticInstance.INSTENCE;
    }
}


class TestStaticInner{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        StaticInnerClass instance = StaticInnerClass.getInstance();
        Constructor<StaticInnerClass> constructor = StaticInnerClass.class.getDeclaredConstructor(null);

        // 忽略私有构造器
        constructor.setAccessible(true);

        StaticInnerClass instance1 = constructor.newInstance();

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

八、枚举(线程安全;可防止反序列化;强烈推荐!!!)

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

优点:线程安全,不用担心反射破坏单例模式

缺点:枚举类占用内存多

构造器源码

public final class Constructor<T> extends Executable {
    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        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;
    }
}

尝试使用反射破坏

/**
 * 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

class TestEnumSingleton{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 反射破坏单例
        EnumSingleton instance = EnumSingleton.INSTANCE;

        // java.lang.NoSuchMethodException
        //Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);

        // 注:通过 jad 反编译得到 有参构造
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);

        // 忽略私有构造器
        constructor.setAccessible(true);

        // java.lang.NoSuchMethodException
        EnumSingleton instance1 = constructor.newInstance();

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

通过 jad 反编译得到 有参构造 

  

 直接报错

单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

踩坑记录——USB键盘睡眠唤醒

踩坑记录——USB键盘睡眠唤醒 目录踩坑记录——USB键盘睡眠唤醒前言1 USB远程睡眠唤醒要注意的几个点2 MCU唤醒之后引起USB异常的几个点结束语前言 前段时间我用一个国产MCU实现了雷蛇键盘的效果&#xff0c;按键支持十键无冲&#xff0c;RGB灯支持单控任意一个灯任意一种颜色…

SpringAMQP (RabbitMQ五种模式 消息转换器)

一、简化Hello World模型实现步骤演示 代码步骤演示如下所示&#xff1a; 消息提供者publisher代码简化&#xff1a; 消息消费者consumer代码简化&#xff1a; 二、Work Queue 工作队列模型 消息提供者代码如下所示&#xff1a; 消息消费者代码如下所示&#xff1a; 消息…

SpringCloudAlibaba【六】微服务架构下的秒杀案例

背景 分布式微服务中的秒杀是怎么实现的呢&#xff1f;接着看下去吧 我们实现一个秒杀微服务&#xff0c;流程逻辑如下 项目搭建 MySQL create database if not exists demo;use demo;drop table if exists skill_goods;create table if not exists skill_goods (id bigint…

1024程序员节|【MySQL从入门到精通】【高级篇】(二十七)外连接和内连接如何进行查询优化呢?join的原理了解一波

您好&#xff0c;我是码农飞哥&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 ❤️ 2.网上优质的Python题库很少…

[面试宝典] Linux常见命令及面试题

前言&#xff1a; &#x1f604;作者简介&#xff1a;小曾同学.com,小伙伴们也可以叫我小曾&#xff0c;一个致力于测试开发的博主⛽️ 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。&#x1f60a; 座右铭&#xff1a;…

C语言 - 你一定能看懂的扫雷万字详解(加入了递归展开和手动标雷的功能)

C语言之扫雷详解&#xff08;包含递归展开和手动标雷功能&#xff0c;非常强大&#xff01;&#xff09; 文章目录一.写在前面二.效果展示三.整体逻辑四.详解1.进入主函数&#xff0c;打印菜单&#xff0c;玩家作出选择2.定义棋盘的数组并进行赋值3.棋盘的展示4.随机布雷5.开始…

前端开发入门--html--Flask

快速开发网站 pip install flaskfrom flask import Flaskapp Flask(__name__)# 创建了网址 /show/info 和 函数index 的对应关系 # 以后用户在浏览器上访问 /show/info&#xff0c;网站自动执行 index app.route("/show/info") def index():return "中国联通&…

TPH-YOLOv5 | 基于Transformer的YOLOv5小目标检测器 | 四头加注意力

论文地址&#xff1a;https://arxiv.org/pdf/2108.11539.pdf 项目地址&#xff1a;https://github.com/cv516Buaa/tph-yolov5 在无人机捕获的场景中进行对象检测是最近的一项热门任务。由于无人机总是在不同的高度航行&#xff0c;物体尺度变化剧烈&#xff0c;给网络优化带来…

NMEA协议解析

文章目录一、NMEA0183协议1、NMEA基本框架2、常用语句1&#xff09;GNGGA2&#xff09;GNGLL3&#xff09;GNGSA4&#xff09;GPGSV5&#xff09;GNRMC6&#xff09;GNVTG7&#xff09;GNZDA8&#xff09;PAIRCLK等二、异或校验和代码1、网址在线计算BCC2、BCC校验和代码一、NM…

Java语言中的异常处理

异常处理 在java语言中&#xff0c;很机智的将异常作为对象来处理&#xff0c;而且定义一个基类java.lang.Throwable作为所有异常类的父类。在这许多类中一般分为两大类&#xff1a; 错误类(Error)和异常类&#xff08;Expception&#xff09;。 如图&#xff1a; 注&#xf…

iNOF在现实网络中的运用,以带反射器的iONF为例

定义 iNOF&#xff08;Intelligent Lossless NVMe Over Fabric&#xff0c;智能无损存储网络&#xff09;是指通过对接入主机的快速管控&#xff0c;将智能无损网络应用到存储系统&#xff0c;实现计算和存储网络融合的技术。 目的 网络转发设备用于传输流量&#xff0c;不同类…

竞争不是内卷,用头脑学习,而非时间

文章目录 用头脑学习&#xff0c;而非时间 前言 一、自由竞争不是内卷 二、内卷都在哪些行业 三、高效学习来大数据梦想联盟 用头脑学习&#xff0c;而非时间 前言 大多数人不懂&#xff0c;不会&#xff0c;不做&#xff0c;才是你的机会&#xff0c;你得行动&#xff…

【Queue】- 从源码分析ArrayDeque及其常用方法

文章目录概述ArrayDeque基础知识ArrayDeque内部结构ArrayDeque的构造方法ArrayDeque的扩容操作ArrayDeque常用方法将ArrayDeque作为双端队列使用时public void addFirst(E e)public void addLast(E e)public boolean offerFirst(E e)public boolean offerLast(E e)public E pol…

动态SLAM论文归纳

持续更新&#xff0c;持续更新 2022 Multi-modal Semantic SLAM for Complex Dynamic Environments 作者&#xff1a;Han Wang, Jing Ying Ko and Lihua Xie, Fellowcode&#xff1a;https://github.com/wh200720041/MMS_SLAM视频&#xff1a;https://www.youtube.com/watch…

web自动化测试——入门篇01

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

并发编程中的原子性,可见性,有序性问题

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中出现的原子性&#xff0c;可见性&#xff0c;有序性问题。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&am…

PyTorch(三)TensorBoard 与 Transforms

文章目录Log一、TensorBoard1. TensorBoard 的安装2. SummaryWriter 的使用① add_scalar() 的使用a. 参数说明b. 函数使用c. 使用 Tensorboard② add_image() 的使用a. 参数说明b. 使用 numpy.array() 对 PIL 图片进行转换c. 使用函数d. 改变 global_step二、Transforms1. Tra…

数据结构 | 时间复杂度与空间复杂度

… &#x1f333;&#x1f332;&#x1f331;本文已收录至&#xff1a;数据结构 | C语言 更多知识尽在此专栏中&#xff01; &#x1f389;&#x1f389;&#x1f389;欢迎点赞、收藏、关注 &#x1f389;&#x1f389;&#x1f389;文章目录&#x1f333;前言&#x1f333;正…

【C++初阶】类和对象(二)

大家好我是沐曦希&#x1f495; 类和对象1.类的6个默认成员函数2.构造函数2.1 概念2.2 特性3.析构函数3.1 概念3.2 特性4.拷贝构造函数4.1 概念4.2 特征1.类的6个默认成员函数 空类&#xff1a;类中一个成员都没有 可是空类真的什么都没有吗&#xff1f; 并不是&#xff0c;任…

STM32关于UART的接收方式

STM32的 UART 一般分为定长接收和不定长接收 定长接收&#xff1a; HAL_UART_Receive():只能接收固定长度的数据&#xff0c;如果超过固定长度的数据只能接收对应长度&#xff0c;如果小于固定长度则不会接收 HAL_UART_Receive_IT():中断方式接收&#xff0c;每接收一个字节…