近期看到到Struts1与Struts2的比較。说Struts1的控制器是单例的,线程不安全的;Struts2的多例的,不存在线程不安全的问题。之后又想到了之前自己用过的HttpHandler。。。

这些类。好像单例的线程安全问题确实是随处可见的。

可是仅仅是知道这个是不安全的,也没有认真分析过。接下来就细致分析下。

一,改动单例模式代码

首先我先写一段单例类的代码:

/*** @ClassName: Sigleton* @Description: 单例类* @author 水田* @date 2015年12月19日 上午10:12:55*/
public class Sigleton {private static Sigleton sigleton;private Sigleton() {// put the initMethod for this class};public static Sigleton getInstance() {// in this demo ,we use "Lazy-load Singleton"if (sigleton == null) {sigleton = new Sigleton();}return sigleton;}
}

这里我使用的是延迟载入,不管是使用延迟载入还是以下的饿汉式:

public class Sigleton {private static final Sigleton sigleton=new Sigleton();private Sigleton() {// put the initMethod for this class};public static Sigleton getInstance() {return sigleton;}
}

这两种情况使用哪一种。要依据实际情况来推断:究竟我是要在此类还没有使用之前进行初始化。还是要在用到它去拿它的时候才初始化。还要看你的实际应用场景。比方说,我这个类超级大,这时候,你部署好了之后。就把它New了。然后放在内存中,十年八年的没人用。这不是浪费么?(情况举的比較极端,就是这个意思吧。只是你要是硬要跟我说。内存啥的越来越不值钱。或者爷有的是钱买内存。我也没办法!

仅仅能送你句,怪不得你没有女朋友!

细致分析这两种方法,然后看看哪里存在线程不安全的因素。

先来瞅瞅第一种,lazy-load方式:

在调用getInstance的时候。先推断,是不是已经被New过了。假设没,那么我new,完了之后返回。想象下,多线程。当在一个线程内。运行到if (sigleton == null),还有一个线程内,好巧啊。也运行到这里。

然后两个线程同一时候推断发现还没这个东西,然后各自new一个。

破坏了我的单实例的原则。

相比另外一种直接new的方式。这样的方法显然是不安全的。可是,假设我要用到这样的lazy-load方式,就要对它进行改进了。

简单改进:

  public static synchronized Sigleton getInstance()

加个keyword。

可是这样的方法还是不好,产生问题的仅仅有sigleton = new Sigleton();如今我锁定了整个方法。有点儿多余了。再改下:

 public final Sigleton getInstance() {// in this demo ,we use "Lazy-load Singleton"if (sigleton == null) {synchronized (this) {sigleton = new Sigleton();}}return sigleton;}

然后改完了之后。我们再从逻辑上看下是不是有漏洞:

还是刚才的问题。俩线程,同一时候运行到if判空的时候,第一个线程由于调度原因,进入同步方法,运行了new操作,第二个线程判空完了之后,进不去,还等在同步方法外面。第一个线程出了同步方法,第二个线程进入了同步方法,又new了一个对象。。

。貌似确实有逻辑楼栋,再改下:

 public final Sigleton getInstance() {// in this demo ,we use "Lazy-load Singleton"if (sigleton == null) {synchronized (this) {if (sigleton == null){sigleton = new Sigleton();}}}return sigleton;}

当第二个线程进入同步方法之后,要不要新new一个对象,还要推断下。

二,After双重检查

    上面的写法一方面实现了Lazy-Load,还有一个方面也做到了并发度非常好的线程安全,一切看上非常完美。

可是二次检查自身会存在比較隐蔽的问题,查了Peter Haggar在DeveloperWorks上的一篇文章,对二次检查的解释非常的具体:


          “双重检查锁定背后的理论是完美的。不幸地是,现实全然不同。

双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型同意所谓的“无序写入”。这也是这些习语失败的一个主要原因。”


    使用二次检查的方法也不是全然安全的,原因是 java 平台内存模型中同意所谓的“无序写入”会导致二次检查失败,所以使用二次检查的想法也行不通了。 

    Peter Haggar在最后提出这样的观点:“不管以何种形式。都不应使用双重检查锁定,由于您不能保证它在不论什么 JVM 实现上都能顺利运行。” 

    问题在哪里? 

    假设线程A运行到了推断对象为空,于是线程A运行去初始化这个对象,但初始化是须要耗费时间的,可是这个对象的地址事实上已经存在了。

此时线程B也运行到了推断不为空。于是直接跳到后面去得到了这个对象。可是,这个对象还没有被完整的初始化。得到一个没有初始化全然的对象有什么用。!


    关于这个Double-Checked Lock的讨论有非常多。眼下公认这是一个Anti-Pattern,不推荐使用! 
                                                                                                                                           (from 网友)

三。怎样安全+单例使用

         首先说明下。饿汉模式是线程安全的。可是在某些情况下。比方,我们不得不使用lazy-load方式,能够考虑以下方法:

1。使用volatilekeyword

  private volatile static Sigleton sigleton;

有些人觉得使用 volatile 的原因是可见性。也就是能够保证线程在本地不会存有instance 的副本,每次都是去主内存中读取。

但事实上是不正确的。使用 volatile 的主要原因是其还有一个特性:禁止指令重排序优化。也就是说。在volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上)。读操作不会被重排序到内存屏障之前。从「先行发生原则」的角度理解的话,就是对于一个 volatile变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

可是特别注意在 Java 5 曾经的版本号使用了volatile 的双检锁还是有问题的。其原因是 Java 5 曾经的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile也不能全然避免重排序。主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5中才得以修复,所以在这之后才干够放心使用 volatile。

2,Initialization on Demand Holder

public class Sigleton {private static class SigletonHolder {private static final Sigleton INSTANCE = new Sigleton();}private Sigleton() {};public static final Sigleton getInstance() {return SigletonHolder.INSTANCE;}
}

    
      在使用sigleton时候。SigletonHolder会被初始化。可是里面的INSTANCE却不会,仅仅有当我们调用getInstance方法的时候。才会去new。

既没有高大上的keyword,逻辑上也好理解。

    细致分析。感觉还是蛮多问题的~

单例模式与线程安全问题浅析相关推荐

  1. spring单例模式与线程安全问题的解决方案

    一句话总结:1:spring容器创建对象的方式是单例的 2:spring单例模式的安全问题是使用ThreadLocal解决的 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行 ...

  2. 单例模式及其线程安全问题

    目录 ​ 1.设计模式 2.饿汉模式 3.懒汉模式 4.线程安全与单例模式 1.设计模式 设计模式是什么? 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案 这些解决方案是众多软件开发人 ...

  3. 单例模式与线程安全问题

    文章目录 本次的学习总结是对Java中的单例设计模式的介绍与解析,对于作为一个开发者而言这个设计模式必须要了解透彻,基本素养吧. 文章目录 文章目录 前言 一.什么是设计模式? 二.什么是单例设计模式 ...

  4. java单例模式之线程安全问题

    单例的目的是为了保证运行时Singleton类只有唯一的一个实例,用于一些较大开销的操作. 饿汉式(没有线程安全问题): ' 由于使用static关键字进行了修饰,只能获取到一个对象,从而达到了单例, ...

  5. Java之线程安全问题浅析

    在java开发中确保线程安全已成为基本要求,线程安全就是指某段代码在多线程环境下能够正确的执行,不会出现数据不一致的情况,反之就是非线程安全. 目前解决线程安全的方式有: 线程安全类,如AtomicI ...

  6. 双重检查锁实现单例模式的线程安全问题

    一.结论 双重校验锁的单例模式代码如下: public class Singleton { private static Singleton singleton; private Singleton( ...

  7. 浅析Java各种变量线程安全问题

    静态方法 非静态方法 静态变量 实例变量 局部变量 静态方法(Static Method) 与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存 ...

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

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

  9. java 恶汉和懒汉_Java单例模式-懒汉式、恶汉式与线程安全问题

    Java的单例模式常见的分为懒汉式.饿汉式.静态内部类.枚举 通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数额控制并节约系统资源. 饿汉式: public cl ...

最新文章

  1. 微信网页授权,获取微信code,获取access_tocken,获取用户信息
  2. 【thymeleaf】【SpringBoot】Thymeleaf 获取.properties中的配置项变量
  3. Vue 中 slot插槽 的使用
  4. 25 PP模块-创建工厂日历报错-请输入年度xxxx和xxxx之间的有效区域
  5. iOS 怎么设置 UITabBarController 的第n个item为第一响应者?
  6. 基于JAVA+SpringBoot+Mybatis+MYSQL的体育馆开放管理系统
  7. 云计算开源软件有哪些?
  8. 爬取网易某只股票2017-01到2018-01的数据
  9. Mutation Testing(变异测试)
  10. 【GIS】GIS概念
  11. arduino连接ps2手柄控制智能小车实践记录-续
  12. ionCube 安装
  13. 一文带小白玩转NFC、门禁卡
  14. 中e管家如何让理财收益最大化
  15. 为啥将phpstudy打开,却访问不了rips
  16. python身份证的秘密_Python|趣解身份证号码的奥秘
  17. 算法竞赛入门【码蹄集进阶塔335题】(MT2001-2025)
  18. Snowflake生成的ID是全局递增唯一么?怎么实现全局递增的唯一ID?
  19. 〖Python 数据库开发实战 - MySQL篇㉑〗- 数据表的外连接
  20. 华为dhcp+ac+ap组网实验

热门文章

  1. is_numeric 检测变量是否为数字或数字字符串
  2. 微软补丁星期二修复120个漏洞,含2个已遭利用的 0day
  3. 如何获得select被选中option的value和text和......
  4. 人工智能:从单细胞生物的智能说起
  5. php初学遇到的问题。
  6. 2016年新运维:优云论《普通运维人员就是秋后的蚂蚱》
  7. 学习Spring(一) -- 配置Spring
  8. 读书记录(持续更新...)
  9. Python XML操作处理
  10. 如何查看系统启动时间-