JUC并发编程学习笔记(十七)彻底玩转单例模式

这篇具有很好参考价值的文章主要介绍了JUC并发编程学习笔记(十七)彻底玩转单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

彻底玩转单例模式

单例中最重要的思想------->构造器私有!

恶汉式、懒汉式(DCL懒汉式!)

恶汉式

package single;
//饿汉式单例(问题:因为一上来就把对象加载了,所以可能会导致浪费内存)
public class Hungry {
    /*
    * 如果其中有大量的需要开辟的空间,如new byte[1024*1024]这些,那么一开始就会加载,而不是需要时才加载,所以非常浪费空间
    *
    * */
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    private Hungry() {
    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉式

DCL懒汉式

完整的双重检测锁模式的单例、懒汉式、DCL懒汉式

package single;

public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread() + "ok");
    }

    private volatile static LazyMan lazyMan;

    //    单线程下确实ok
    public static LazyMan getInstance() {
//        加锁、锁整个类
//        双重检测锁模式的单例、懒汉式、DCL懒汉式
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan == null) {
                    lazyMan = new LazyMan();//不是原子性操作

                }
            }
        }
        return lazyMan;
    }
    /*
     * 1、分配内存空间
     * 2、执行构造方法,初始化对象
     * 3、把这个对象指向这个空间
     *
     * 期望的结果:1、2、3
     * 但是由于指令重排可能导致结果为1、3、2,这在cpu中是没问题的
     * 线程A:1、3、2
     * 线程B如果在线程A执行到3时开始执行判断是否为null,由于已经占用空间了,所以会被判断为不为空,但实际还未初始化对象,实际结果还是为null
     *
     *
     * */

    //    多线程并发测试
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }

}

但是有反射!只要有反射,任何的代码都不安全,任何的私有关键字都是摆设

正常的单例模式:

/*
* 正常的单例模式创建的都为同一个对象,并且该对象全局唯一
* 只执行一次创建,并且对象都是同一个
* Thread[main,5,main]ok
* true
* */
LazyMan instance1 = LazyMan.getInstance();
LazyMan instance2 = LazyMan.getInstance();
System.out.println(instance2==instance1);

反射破坏单例:

/*
* 通过反射破坏单例
* 执行两个创建,两个不同的对象
* Thread[main,5,main]ok
  Thread[main,5,main]ok
  false
* */
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance2 == instance1);

怎么去解决这种破坏呢?

首先反射走了无参构造器,我们可以在构造器中进行加锁判断是否已经存在了对象。

private LazyMan() {
    //通过构造器来加锁判断防止反射破坏
    synchronized (LazyMan.class){
        if (lazyMan!=null){
            throw new RuntimeException("不要试图使用反射破坏单例模式");
        }
    }

}

通过反射破坏单例模式

道高一尺,魔高一丈

1、通过普通的反射来破坏单例模式

Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = LazyMan.getInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);

JUC并发编程学习笔记(十七)彻底玩转单例模式

解决方法:通过构造器加锁解决

private LazyMan() {
    //通过构造器来加锁判断防止反射破坏
    synchronized (LazyMan.class){
        if (lazyMan == null){

        }else {
            throw new RuntimeException("不要试图使用反射破坏单例模式");
        }

    }
}

2、通过反射创建两个类来破坏单例模式

Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan lazyMan1 = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);

解决方法:设置一个外部私有变量,在构造方法中通过外部私有变量来操作

//创建一个外部的标,用于防止通过newInstance破坏单例模式
private static boolean flg = true;
private LazyMan() {
    //通过构造器来加锁判断防止反射破坏
    synchronized (LazyMan.class){
        if (flg){
            flg = false;
        }else {
            throw new RuntimeException("不要试图使用反射破坏单例模式");
        }
    }
}

3、通过反射字段来将外部私有变量修改。

Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
//通过反射修改内部私有变量
Field flg1 = LazyMan.class.getDeclaredField("flg");
flg1.setAccessible(true);

//通过反射的newInstance创建的两个对象依旧破坏了单例模式
LazyMan instance1 = declaredConstructor.newInstance();
//通过反射字段对单例模式进行破坏
flg1.set(instance1,true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance2 == instance1);

解决方法,通过枚举类型!枚举类型自带单例模式,禁止反射破坏

package single;

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

//枚举类
public enum EnumDemo {
    INSTANCE;
    public EnumDemo getInstance(){
        return INSTANCE;
    }
}
class EnumDemoTest{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<EnumDemo> declaredConstructor = EnumDemo.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumDemo enumDemo1 = declaredConstructor.newInstance();
        EnumDemo enumDemo2 = declaredConstructor.newInstance();
        System.out.println(enumDemo1);
        System.out.println(enumDemo2);
    }
}

发现抱错,没有对应的无参构造

JUC并发编程学习笔记(十七)彻底玩转单例模式

但是idea编译的源码中是由无参构造的

JUC并发编程学习笔记(十七)彻底玩转单例模式

idea欺骗了我们,那么编译好的类到底有没有无参构造,通过javap -p反编译源码查看所以方法

JUC并发编程学习笔记(十七)彻底玩转单例模式

可以看到,也有空参的构造方法,也就意味了反编译源码也欺骗了你,所以我们通过更专业的工具来查看,使用jad查看。

JUC并发编程学习笔记(十七)彻底玩转单例模式

查看当前目录新生成的java文件可以发现,通过jad反编译的源码的构造函数时个有参构造函数


// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumDemo.java

package single;


public final class EnumDemo extends Enum
{
public static EnumDemo[] values()
{
    return (EnumDemo[])$VALUES.clone();
}

public static EnumDemo valueOf(String name)
{
    return (EnumDemo)Enum.valueOf(single/EnumDemo, name);
}

private EnumDemo(String s, int i)
{
    super(s, i);
}

public EnumDemo getInstance()
{
    return INSTANCE;
}

public static final EnumDemo INSTANCE;
private static final EnumDemo $VALUES[];

static 
{
    INSTANCE = new EnumDemo("INSTANCE", 0);
    $VALUES = (new EnumDemo[] {
        INSTANCE
    });
}
}

我们尝试在反射中加入这两个参数类

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

可以发现,它根据我们预想的结果抛出一个异常

JUC并发编程学习笔记(十七)彻底玩转单例模式

在newInstance方法中如果时枚举类就会抛出这个异常,这是从反射层面限制了对枚举类单例模式的破坏!!

JUC并发编程学习笔记(十七)彻底玩转单例模式文章来源地址https://www.toymoban.com/news/detail-750349.html

到了这里,关于JUC并发编程学习笔记(十七)彻底玩转单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JUC并发编程学习笔记(十四)异步回调

    Future设计的初衷:对将来的某个事件的结果进行建模 在Future类的子类中可以找到CompletableFuture,在介绍中可以看到这是为非异步的请求使用一些异步的方法来处理 点进具体实现类中,查看方法,可以看到CompletableFuture中的异步内部类,里面是实现的异步方法 以及一些异步方法

    2024年02月05日
    浏览(45)
  • JUC并发编程学习笔记(十五)JMM

    请你谈谈对Volatile的理解 Volatile是java虚拟机提供的 轻量级的同步机制 1、保证可见性 2、不保证原子性 3、禁止指令重排 什么是JMM JVM-java虚拟机 JMM-java内存模型,不存在的东西,概念!约定 关于JMM的一些同步的约定: 线程解锁前,必须把共享变量 立刻 刷回主存 线程加锁前,

    2024年02月05日
    浏览(49)
  • JUC并发编程学习笔记(十六)Volatile

    保证可见性 使用了volatile,即可保证它本身可被其他线程的工作内存感知,即变化时也会被同步变化。 不保证原子性 原子性:不可分割 线程A在执行任务时是不可被打扰的,也不能被分割,要么同时成功,要么同时失败。 每次结果也不一样。 如果不加Lock加synchronize

    2024年02月05日
    浏览(96)
  • JUC并发编程学习笔记(四)8锁现象

    八锁-就是关于锁的八个问题 锁是什么,如何判断锁的是谁 对象、class模板 深刻理解锁 锁的东西无外乎就两样:1、同步方法的调用者,2、Class模板。 同一个锁中,只有当前线程资源释放后才会被下一个线程所接手。 同步方法的调用者是两个不同的实例时,互不相关。 静态

    2024年02月06日
    浏览(52)
  • JUC并发编程学习笔记(六)Callable(简单)

    callable接口和runnable接口类似,都是为了执行另外一条线程而设计的,区别是Runnable不会返回结果也不会抛出异常。 1、可以有返回值 2、可以抛出异常 3、方法不同;run()/call(); Runnable 实现Runnable接口,重写run方法,无返回值 Callable 实现Callable接口,重写call方法,有返回值,可

    2024年02月06日
    浏览(42)
  • JUC并发编程学习笔记(十)线程池(重点)

    线程池:三大方法、七大参数、四种拒绝策略 池化技术 程序的运行,本质:占用系统的资源!优化资源的使用!- 池化技术(线程池、连接池、对象池......);创建和销毁十分消耗资源 池化技术:事先准备好一些资源,有人要用就拿,拿完用完还给我。 线程池的好处: 1、

    2024年02月06日
    浏览(44)
  • JUC并发编程学习笔记(一)认知进程和线程

    进程 一个程序,如QQ.exe,是程序的集合 一个进程往往可以包含多个线程,至少包含一个 java默认有两个线程,GC垃圾回收线程和Main线程 线程:一个进程中的各个功能 java无法真正的开启线程,因为java是运行在虚拟机上的,所以只能通过C++,通过native本地方法调用C++开启线程

    2024年02月06日
    浏览(53)
  • JUC并发编程学习笔记(十八)深入理解CAS

    什么是CAS 为什么要学CAS:大厂你必须深入研究底层!有所突破! java层面的cas-------compareAndSet compareAndSet(int expectedValue, int newValue) 期望并更新,达到期望值就更新、否则就不更新! Unsafe类 java不能直接操作内存,但是可以调用c++,c++可以操作内存,java可以通过native定义

    2024年02月05日
    浏览(59)
  • JUC并发编程学习笔记(十二)Stream流式计算

    什么是Stream流式计算 大数据:存储+计算 集合、MySql这些的本质都是存储东西的; 计算都应该交给流来操作! 一个案例说明:函数式接口、lambda表达式、链式编程、Stream流式计算

    2024年02月05日
    浏览(47)
  • JUC并发编程学习笔记(七)常用的辅助类

    CountDownLatch 这是一个JUC计数器辅助类,计数器有加有减,这是减。 使用方法 使用前 可能会在所有人没出去之前关门 使用后 不在乎谁先出去,但是一定要总数等于0后才会关门 原理 countDownLatch.countDown();//总数减1 countDownLatch.await();//等待总数变为0才会往下执行,相当于阻塞当

    2024年02月06日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包