彻底玩转单例模式

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

恶汉式、懒汉式(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);

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

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

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

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

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

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

查看当前目录新生成的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);

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

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

JUC并发编程学习笔记(十七)彻底玩转单例模式的更多相关文章

  1. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  2. 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题

    再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...

  3. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  4. 并发编程学习笔记(15)----Executor框架的使用

    Executor执行已提交的 Runnable 任务的对象.此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节.调度等)分离开来的方法.通常使用 Executor 而不是显式地创建 ...

  5. 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理

    1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...

  6. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  7. 并发编程学习笔记(11)----FutureTask的使用及实现

    1. Future的使用 Future模式解决的问题是.在实际的运用场景中,可能某一个任务执行起来非常耗时,如果我们线程一直等着该任务执行完成再去执行其他的代码,就会损耗很大的性能,而Future接口 ...

  8. 并发编程学习笔记(12)----Fork/Join框架

    1. Fork/Join 的概念 Fork指的是将系统进程分成多个执行分支(线程),Join即是等待,当fork()方法创建了多个线程之后,需要等待这些分支执行完毕之后,才能得到最终的结果,因此joi ...

  9. 并发编程学习笔记(10)----并发工具类CyclicBarrier、Semaphore和Exchanger类的使用和原理

    在jdk中,为并发编程提供了CyclicBarrier(栅栏),CountDownLatch(闭锁),Semaphore(信号量),Exchanger(数据交换)等工具类,我们在前面的学习中已经学习并 ...

  10. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

随机推荐

  1. failed (2: No such file or directory) in /var/www/QQ_Music/nginx.conf:18

    错误原因 解决方案 引入文件 /www/server/nginx/conf/mime.types;

  2. openpyxl 设置单元格自动换行

    解决方案 openpyxl的alignment函数中的参数:wrapText=True,就可以了 from openpyxl.styles import Alignment worksheet.cel ...

  3. Unity的IFilterBuildAssemblies:深入解析与实用案例

    Unity IFilterBuildAssemblies Unity IFilterBuildAssemblies是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自定义哪些程序集需要 ...

  4. nodejs端模块化方式comomjs详解

    nodejs端实现模块化的方式通常是通过commonjs,使用模块化可以复用js代码,使得逻辑结构更为清晰. commonjs的语法规则如下通过 module.exports 或者 exports 导 ...

  5. 【必看!】阿里云推出QWen-7B和QWen-7b-Chat,开放免费商用!

    阿里云于8月3日宣布开源两款重要的大型模型--QWen-7B和QWen-7b-Chat.这两款模型的参数规模达到了令人瞩目的70亿,并且已经在Hugging Face和ModelScope平台上开放, ...

  6. 一款开源免费、更符合现代用户需求的论坛系统:vanilla

    对于个人建站来说,WordPress相信很多读者都知道了.但WordPress很多时候我们还是用来建立自主发布内容的站点为主,适用于个人博客.企业主站等.虽然有的主题可以把WordPress变为论坛, ...

  7. [jmeter]简介与安装

    简介 JMeter是开源软件Apache基金会下的一个性能测试工具,用来测试部署在服务器端的应用程序的性能. 安装 安装jmeter 从 官网 下载jmeter的压缩包 安装jdk并配置 JAVA_H ...

  8. 浏览器Xbox 云游戏教程

    我这里使用的是韩国的地方因为延迟和网速会比较快 Xbox 云游戏韩国网站 Xbox.com에서 Xbox Cloud Gaming(베타) 首先插件商店下载一个油猴插件 在系统语言和时区改为韩国 在  ...

  9. vue3+ts Axios封装—重复请求拦截

    创建好vue3项目 1.安装Axios与Element Plus Axios安装 npm install axios Element Plus 安装 官网入口:https://element-plus ...

  10. HDU 1171 0-1背包

    最近感觉DP已经完全忘了..各种爆炸,打算好好复习一发,0-1背包开始 Big Event in HDU Problem Description Nowadays, we all know that ...