当前位置: 首页 > news >正文

玩转单例模式

目录

1. 饿汉式 

2. 懒汉式

3. volatile解决指令重排序

4. 反射破坏单例模式

5. 枚举创建单例模式


所谓单例模式,就是是某个类的实例对象只能被创建一次,单例模式两种实现:饿汉式懒汉式

1. 饿汉式 

所谓饿汉式,顾名思义,很饿,迫不及待,就是在类加载时就已经创建好了对象。优点是没有线程安全问题。缺点是浪费资源空间。不管用不用,对象都会被提前创建出来。

代码演示

// 饿汉模式
class Singleton {// 私有构造方法private Singleton() {}private static final Singleton singleton = new Singleton();public static Singleton getInstance() {return singleton;}}

2. 懒汉式

所谓懒汉式,就是在当方法调用时才会去创建对象,优点是方法调用时才创建,不浪费空间,缺点是有线程安全问题。

class Singleton {private Singleton() {}private static Singleton singleton = null;public static Singleton getInstance() {// 判断对象是否创建if(singleton==null) {singleton = new Singleton();}return singleton;}
}

代码测试(创建10个线程):

public class Demo {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> Singleton.getInstance()).start();}}
}class Singleton {private Singleton() {System.out.println(Thread.currentThread().getName()+"创建了对象");}private static Singleton singleton;public static Singleton getInstance() {if(singleton==null) {singleton = new Singleton();}return singleton;}
}

结果:

如上代码,先判断对象是否为空,再创建对象。这个过程在多线程中就会出现多个线程同时判断为空并创建对象的情况。那既然判断是否为空这个过程会有多个线程同时执行,我们可以通过加锁解决。 

class Singleton {private Singleton() {}private static Singleton singleton;public static Singleton getInstance() {// 加锁synchronized (Singleton.class) {if(singleton==null) {singleton = new Singleton();}    }return singleton;}
}

代码测试(10个线程):

public class Demo {public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> Singleton.getInstance()).start();}}
}class Singleton {private Singleton() {System.out.println(Thread.currentThread().getName()+"创建了对象");}private static Singleton singleton;public static Singleton getInstance() {synchronized (Singleton.class) {if(singleton==null) {singleton = new Singleton();}}return singleton;}
}

结果:

通过测试,懒汉模式创建多个对象问题已经解决,但是又引入了一个新问题,那就是性能问题。上面通过加锁确实解决了多线程情况下创建多个实例对象问题。但是这种代码逻辑每个线程都要去获取锁或者没获取到阻塞等待,性能大大降低。不妨在加锁外面再包裹一层判断对象是否为空。这样判断不为空的线程就可以直接返回对象,无需再去获取锁或阻塞等待了。

class Singleton {private Singleton() {}private static Singleton singleton;public static Singleton getInstance() {if(singleton==null) {synchronized (Singleton.class) {if(singleton==null) {singleton = new Singleton(); // 不是一个原子性操作}}    }return singleton;}
}

除此之外,上面代码还有一个问题,那就是new一个实例对象时并不是一个原子性操作。总共需要三步:

  1. 先分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

正常创建对象步骤是1->2->3,但有时JVM为了提高性能,会将这三个步骤调整,即指令重排序,这种情况下就会出现1->3->2,如果是是后者,假如有两个线程A,B,若A线程正在创建对象的第二步(按132,把对象指向这个空间,此时对象还没有初始化),此时若线程B正在判断最外层的对象是否为空时就会认为不为空,立即返回该对象。实际上该对象还没有被构造,只是提前分配占用了这块内存空间。

指令重排序是JVM为了提高性能,并且在保证最终结果正确的前提下,对代码执行顺序进行了调整。

3. volatile解决指令重排序

volatile虽然不能解决原子性问题,但是可以禁止指令重排序和内存可见性。

最终的代码如下所示:

class Singleton {private Singleton() {}private volatile static Singleton singleton;public static Singleton getInstance() {if(singleton==null) {synchronized (Singleton.class) {if(singleton==null) {singleton = new Singleton();}}    }return singleton;}
}

说好了玩转单例模式,怎么能到这就结束呢。上面的单例模式看似已经优化的很好了,但是由于反射的存在,可以将构造方法从private变成public。这种情况下就直接可以通过构造方法创建,不用去调用静态方法。

4. 反射破坏单例模式

public class Demo {public static void main(String[] args) throws Exception {// null表示空参构造,获取空参构造器Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);// 设置为true会无视私有构造器declaredConstructor.setAccessible(true);Singleton singleton = declaredConstructor.newInstance();Singleton singleton1 = declaredConstructor.newInstance();System.out.println(singleton);System.out.println(singleton1);}
}class Singleton {private Singleton() {}private volatile static Singleton singleton;public static Singleton getInstance() {synchronized (Singleton.class) {if(singleton==null) {singleton = new Singleton();}}return singleton;}
}

可以看出通过反射确实创建出了多个实例对象。 

解决办法也很多,比如在私有构造方法里加锁判断对象是否为空,或者设置一个标志位来判断对象是否为空。但是这两种方法使用反射也还是都可以破坏,比如我可以通过反射每次修改标志位。通过查看反射创建实例对象的源码可以发现,如果使用反射去创建枚举类的实例对象就会报错。这说明是不能通过反射来创建枚举对象的,这样一下就阻止了反射破坏单例。

5. 枚举创建单例模式

由于使用反射的newInstance()方法创建枚举对象时,这个方法内会判断当前类是否是枚举类,是的话直接就抛异常了。当然可能还有其他原因,正是这样,反射是不能创建枚举对象的。

我们来尝试使用反射来破坏单例的枚举类,看会发生什么。

如下,通过编译生成的class类以及反编译中均显示有私有无参构造。那么我们从无参构造出发,来通过反射创建枚举对象。代码如下:

@Test
void test5() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Constructor<Single> declaredConstructor = Single.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Single single = declaredConstructor.newInstance();Single single1 = declaredConstructor.newInstance();System.out.println(single);System.out.println(single1);
}

但是结果并不尽人意,报错意思是没有这个空参构造方法,这就奇了怪了,从上面的class文件以及反编译中都显示有啊,难道显示的骗了我们吗。难道没有这个无参构造吗?

再换一个jad反编译工具。

 原来并不是无参构造,而是这个隐藏其中的有参构造呀。接下来按照这个有参构造来通过反射创建对象。

@Test
void test5() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Constructor<Single> declaredConstructor = Single.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);Single single = declaredConstructor.newInstance();Single single1 = declaredConstructor.newInstance();System.out.println(single);System.out.println(single1);}

结果

这个结果跟之前源码中反射创建枚举实例对象的异常一样。这还真说明说明java居然骗了我们哈哈。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Unity教程】使用 Animation Rigging实现IK制作程序化的动画
  • 使用模板导出excel文件
  • C++ 124类和对象_运算符重载_赋值
  • 前端工程化-05.Vue项目开发流程
  • Jenkins持续集成工具学习
  • LeetCode 242 有效的字母异位词
  • 使用X-AnyLabeling自动标注数据集(以yolov10模型为例,多图详细介绍)
  • 【商城源码开发要多久】
  • 时间记录内核模块
  • 【AI/算法类】OPPO 2025届秋招笔试题(B卷)
  • OpenCV的编译(MinGW)
  • 大模型是如何升级的呢?技术?应用?
  • 微信小程序开发:基础架构与配置文件
  • opencv 控制鼠标键盘实现功能setMouseCallback
  • 江科大/江协科技 STM32学习笔记P24
  • JavaScript-如何实现克隆(clone)函数
  • 【347天】每日项目总结系列085(2018.01.18)
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • Apache的80端口被占用以及访问时报错403
  • JavaScript新鲜事·第5期
  • Sublime Text 2/3 绑定Eclipse快捷键
  • 对象引论
  • 来,膜拜下android roadmap,强大的执行力
  • 码农张的Bug人生 - 初来乍到
  • 前言-如何学习区块链
  • 人脸识别最新开发经验demo
  • 少走弯路,给Java 1~5 年程序员的建议
  • 提醒我喝水chrome插件开发指南
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​卜东波研究员:高观点下的少儿计算思维
  • #define与typedef区别
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (52)只出现一次的数字III
  • (AngularJS)Angular 控制器之间通信初探
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (搬运以学习)flask 上下文的实现
  • (论文阅读11/100)Fast R-CNN
  • (十五)使用Nexus创建Maven私服
  • (一)认识微服务
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • ****三次握手和四次挥手
  • .Net接口调试与案例
  • @31省区市高考时间表来了,祝考试成功
  • [ vulhub漏洞复现篇 ] ECShop 2.x / 3.x SQL注入/远程执行代码漏洞 xianzhi-2017-02-82239600
  • [100天算法】-x 的平方根(day 61)
  • [20150707]外部表与rowid.txt
  • [2016.7.Test1] T1 三进制异或
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [ARC066F]Contest with Drinks Hard
  • [ASP.NET MVC]Ajax与CustomErrors的尴尬
  • [Java基础]—JDBC
  • [Linux] 一文理解HTTPS协议:什么是HTTPS协议、HTTPS协议如何加密数据、什么是CA证书(数字证书)...