先看代码:

package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {//1:读取instance的值instance = new Singleton2();//2: 实例化instance}return instance;}}

package com.roocon.thread.t5;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MultiThreadMain {public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(20);for (int i = 0; i< 20; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+":"+Singleton2.getInstance());}});}
     threadPool.shutdown();
} }

运行结果:

pool-1-thread-4:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-14:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-10:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-8:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-5:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-12:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-1:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-9:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-6:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-2:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-16:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-3:com.roocon.thread.t5.Singleton2@1c208db1
pool-1-thread-17:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-13:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-18:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-7:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-20:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-11:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-15:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-19:com.roocon.thread.t5.Singleton2@6519891a

发现,有个实例是Singleton2@1c208db1,也就说明,返回的不是同一个实例。这就是所谓的线程安全问题。

解释原因:对于以上代码注释部分,如果此时有两个线程,线程A执行到1处,读取了instance为null,然后cpu就被线程B抢去了,此时,线程A还没有对instance进行实例化。

因此,线程B读取instance时仍然为null,于是,它对instance进行实例化了。然后,cpu就被线程A抢去了。此时,线程A由于已经读取了instance的值并且认为它为null,所以,

再次对instance进行实例化。所以,线程A和线程B返回的不是同一个实例。

那么,如何解决呢?

1.在方法前面加synchronized修饰。这样肯定不会再有线程安全问题。

package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static synchronized Singleton2 getInstance(){if(instance == null) {//1instance = new Singleton2();//2}return instance;}}

但是,这种解决方式,假如有100个线程同时执行,那么,每次去执行getInstance方法时都要先获得锁再去执行方法体,如果没有锁,就要等待,耗时长,感觉像是变成了串行处理。因此,尝试其他更好的处理方式。

2. 加同步代码块,减少锁的颗粒大小。我们发现,只有第一次instance为null的时候,才去创建实例,而判断instance是否为null是读的操作,不可能存在线程安全问题,因此,我们只需要对创建实例的代码进行同步代码块的处理,也就是所谓的对可能出现线程安全的代码进行同步代码块的处理。

package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){instance = new Singleton2();}}return instance;}}

但是,这样处理就没有问题了吗?同样的原理,线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null,于是,它开始执行同步代码块中的代码,对instance进行实例化。此时,线程A获得cpu,由于线程A之前已经判断instance值为null,于是开始执行它后面的同步代码块代码。它也会去对instance进行实例化。

这样就导致了还是会创建两个不一样的实例。

那么,如何解决上面的问题。

很简单,在同步代码块中instance实例化之前进行判断,如果instance为null,才对其进行实例化。这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。

再次分析上面的场景:

线程A和线程B,线程A读取instance值为null,此时cpu被线程B抢去了,线程B再来判断instance值为null。于是,它开始执行同步代码块代码,对instance进行了实例化。这是线程A获得cpu执行权,当线程A去执行同步代码块中的代码时,它再去判断instance的值,由于线程B执行完后已经将这个共享资源instance实例化了,所以instance不再为null,所以,线程A就不会再次实行实例化代码了。

package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static Singleton2 instance;public static synchronized Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){if (instance == null){instance = new Singleton2();}}}return instance;}}

但是,双重检查加锁并不代码百分百一定没有线程安全问题了。因为,这里会涉及到一个指令重排序问题。instance = new Singleton2()其实可以分为下面的步骤:

1.申请一块内存空间;

2.在这块空间里实例化对象;

3.instance的引用指向这块空间地址;

指令重排序存在的问题是:

对于以上步骤,指令重排序很有可能不是按上面123步骤依次执行的。比如,先执行1申请一块内存空间,然后执行3步骤,instance的引用去指向刚刚申请的内存空间地址,那么,当它再去执行2步骤,判断instance时,由于instance已经指向了某一地址,它就不会再为null了,因此,也就不会实例化对象了。这就是所谓的指令重排序安全问题。那么,如何解决这个问题呢?

加上volatile关键字,因为volatile可以禁止指令重排序。

package com.roocon.thread.t5;public class Singleton2 {private Singleton2(){}private static volatile Singleton2 instance;public static Singleton2 getInstance(){if(instance == null) {synchronized (Singleton2.class){if (instance == null){instance = new Singleton2();}}}return instance;}
}

转载于:https://www.cnblogs.com/mike-JP/p/11084316.html

单例模式中的懒汉式以及线程安全性问题相关推荐

  1. 单例模式中的懒汉式和饿汉式对比

    "懒汉式"是在你真正用到的时候,需要调用getInstance()方法的时候,才会去创建这个单例对象: "饿汉式"是不管你需不需要用到,都会去new Singl ...

  2. 单例模式中懒汉式和饿汉式实现

    单例模式的代码实现 1.懒汉式代码实现: public class Singleton {//默认不会实例化,什么时候用就什么时候newprivate static Singleton instanc ...

  3. 线程的同步之Synchronized在单例模式中的应用

    synchronized在单例模式中的使用 在单例模式中有一种懒汉式的单例,就是类初始化的时候不创建对象.等第一次获取的时候再创建对象.这种单例在单线程下是没有问题的获取的也都是同一个对象.但是如果放 ...

  4. Java中枚举的线程安全性及序列化问题

    转载自  Java中枚举的线程安全性及序列化问题 Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序 ...

  5. Java单例模式中的线程安全问题

    在Java中单例模式被分为懒汉式和饿汉式,饿汉式会在单例类加载时就创建实例而懒汉式则延迟实例化,在使用到单例实例的时候才实例化.在单线程的程序里两张方式没什么区别,多线程的话懒汉式会有线程安全问题.先 ...

  6. Java多线程:多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题

    Java多线程:多线程同步安全问题的 "三" 种处理方式 ||多线程 "死锁" 的避免 || 单例模式"懒汉式"的线程同步安全问题 每博一文 ...

  7. Java并发编程(五):Java线程安全性中的对象发布和逸出

    发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...

  8. Java Singleton类中的线程安全性的示例代码

    Java Singleton类中的线程安全性的示例代码 Singleton是最广泛使用的创建设计模式之一,用于限制应用程序创建对象.在实际应用程序中,数据库连接或企业信息系统(EIS)等资源是有限的, ...

  9. 多线程中的互斥控制程序代码_Java中的并发——线程安全性

    一.什么是线程安全性? 当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,则这个类是线程安全的 ...

最新文章

  1. SAP QM 样品废弃后如何删除physical samples记录?
  2. iOS唯一标示符引导
  3. hdu 1053 Entropy (哈夫曼树)
  4. 编写高质量代码:改善Java的151个建议五(类、对象、方法)31-51
  5. python基础笔记_python基础学习笔记(九)
  6. 北京工业计算机考研科目,2020北京工业大学计算机考研初试科目、参考书目、招生人数汇总...
  7. 人工智能 信道估计 深度学习_DEMO演示|基于IVP02D 人工智能工作站的深度学习引擎,实现人群热力估计...
  8. cmd imp导入dmp文件_这是一篇长篇入门级数据库讲解:oracle数据库数据导入导出步骤...
  9. 如果数据库也有一个元宇宙,应该会是什么样子?
  10. 10天学安卓-第七天
  11. Springboot集成RabbitMQ一个完整案例
  12. pdf转HTML出现乱码,PDF转Word出现乱码解决方法
  13. 利润表模板excel_分享用了8年的excel记账系统,一键录入,多表生成,记账很简单...
  14. 计算机win键是哪里,键盘Win键在哪里
  15. 机器学习-分类-线性分类器
  16. 提现微信和提现到支付宝
  17. Fst, pi, TajimaD plink 计算
  18. 接口与多态:模拟物流快递系统程序设计实验
  19. python打开谷歌浏览器新标签页_selenium chrome在新标签页打开链接的方法
  20. c语言共阴极数码管数字6,用51单片机C语言编写程序实现6位共阴极数码管循环显示0123456789ABCDEF,六个数码管是连续不同的六个数?...

热门文章

  1. 圣杯布局与双飞翼布局全解
  2. Android开发:怎样把Android studio中的Library公布到Jcenter
  3. 世界级Oracle专家Jonathan Lewis:我很为DBA们的未来担心(图灵访谈)
  4. Seafile 1.4 发布,文件同步和协作平台
  5. 计算几何算法概览 (一)
  6. ORACLE 中为什么要把列名都转换成大写字母?
  7. C#中显/隐式实现接口及其访问方法
  8. native2ascii插件配置
  9. 使用VIA声卡 运行生化危机5无声音的解决方案 无需卸载旧驱动
  10. leetcode算法题--一周中的第几天