单例模式可能是我们平常工作中最常用的一种设计模式了。单例模式解决的问题也很常见,即如何创建一个唯一的对象。但想安全的创建它其实并不容易,还需要一些思考和对JVM的了解。

  1.首先,课本上告诉我,单例这么写

  

 public class Singleton {

     private static Singleton instance;

     private Singleton() {
} public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

  这段代码最大的问题就是它并不是线程安全的。即在多线程情况下可能new 出多个对象。试想有两个线程同时执行到了第9行,由于没有锁机制,那么两个线程都会进入,就会new出多个对象。

 public static CountDownLatch latch = new CountDownLatch(2);

     public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() { @Override
public void run() {
latch.countDown();
try {
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Singleton.getInstance());
}
}).start();
}
}

  我用上面代码来演示 第一种单例写法的结果。最后会调用Object的toString方法来打印Singleton对象的hashcode

  结果如下

  

第一次结果:
com.deng.pp.Singleton@33bbe97
com.deng.pp.Singleton@11989480 第二次结果:
com.deng.pp.Singleton@1c0956b9
com.deng.pp.Singleton@5e70125b 第三次结果:
com.deng.pp.Singleton@5e70125b
com.deng.pp.Singleton@1c0956b9 第四次结果:
com.deng.pp.Singleton@1c0956b9
com.deng.pp.Singleton@1c0956b9 第五次结果:
com.deng.pp.Singleton@1c0956b9
com.deng.pp.Singleton@1c0956b9

  可以看出,单例代码1确实会存在new 出多个对象的情况。

  将单例代码1的getInstance方法 改成如下,对getInstance方法加synchronized 关键字

  

public static synchronized Singleton getInstance() {
  if (instance == null) {
    instance = new Singleton();
  }
  return instance;
}

  下面是5次测试结果

  

第一次:
com.deng.pp.Singleton@33bbe97
com.deng.pp.Singleton@33bbe97
第二次
com.deng.pp.Singleton@12ceba90
com.deng.pp.Singleton@12ceba90
第三次
com.deng.pp.Singleton@5e70125b
com.deng.pp.Singleton@5e70125b
第四次
com.deng.pp.Singleton@1c0956b9
com.deng.pp.Singleton@1c0956b9
第五次
com.deng.pp.Singleton@1c0956b9
com.deng.pp.Singleton@1c0956b9

  可以确定synchronized确实起了作用。这么做是可以work的,执行结果也没有什么错误。但它有一个最大问题是效率问题。每个线程调用getInstance方法是都要去判断是否有其他线程在执行这个方法,即使instance已经存在也需要去判断是否有线程在方法里面。如果有,就要在外边等。而实际上只需要在new 对象之前等就可以了。根据这个就有了下面的方法:双重检查锁

  

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

  来分析一下,假设两个线程到达getInstance方法,线程1先获得了锁,进入初始化方法。线程2因未获得锁在外边等待,线程1出去后,线程2进入同步块,instance不是null,return,完美。

  但是结果可能并不是这样,因为 对象的new操作并不是 原子 的。JVM new 对象的过程大致如下

  1.在堆上分配一块内存空间  2.实例化类放入1分配的内存空间 3.把引用赋给instance

  如果按照123的顺序,上面那段代码就没有问题。但JVM中存在指令重排,即编译器对代码进行优化,改变不相互依赖的代码的执行顺序。上述1,2,3中第三步并不依赖于第二步,即可能存在132这样的顺序。

  那么这种顺序下,线程1执行到3。线程2进入方法,此时由于instance已被赋值,所以不为null。直接return,此时return的对象是不正确的,因为线程1还没有将对象完全初始化完。

  (很抱歉,在我的环境下并没有重现这种问题,如果有其他的可以测试出这种问题的方法,望不吝赐教。)

  解决办法是将instance字段改成

    private volatile static Singleton instance;

  volatile 关键字会禁止指令重排序,从而保证了单例正确性。

  下面的方法也可以实现单例,因为SINGLETON为static的所以在类加载时就会初始化,final保证了只会赋一遍值。项目较小时可以用,很方便,类很多的时候如果都上来就加载可能就很浪费资源了。

public static final Singleton SINGLETON = new Singleton();

  Effective Java作者推荐了一种更好更安全的写法。

public class Singleton {

    private Singleton() {
} public enum Instance{
INSTANCE; private Singleton singleton; Instance() {
singleton = new Singleton();
} public Singleton getInstance(){
return singleton;
}
}
}

  最近才开始写博客,才疏学浅,如文中有任何错误请留言交流。谢谢~

写一个安全的Java单例的更多相关文章

  1. 从一个简单的Java单例示例谈谈并发

    一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static Un ...

  2. 从一个简单的Java单例示例谈谈并发 JMM JUC

    原文: http://www.open-open.com/lib/view/open1462871898428.html 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这 ...

  3. 熟悉的味道——从Java单例写到C++单例

    设计模式中,单例模式是常见的一种.单例模式需要满足以下两个条件: 保证一个类只能创建一个示例: 提供对该实例的全局访问点. 关于单例最经典的问题就是DCL(Double-Checked Lock),今 ...

  4. java单例-积木系列

    一步步知识点归纳吧,把以前似懂非懂,了解表面,知道点不知道面的知识归一下档.   懒汉式单例: 私有化构造函数,阻止外界实例话对象,调用getInstance静态方法,判断是否已经实例化. 为什么是懒 ...

  5. Java单例类的简单实现

    对于java新手来说,单例类给我的印象挺深,之前一道web后台笔试题就是写单例类.*.*可惜当时不了解. 在大部分时候,我们将类的构造器定义成public访问权限,允许任何类自由创建该类的对象.但在某 ...

  6. 转:java单例设计模式

    本文转自:http://www.cnblogs.com/yinxiaoqiexuxing/p/5605338.html 单例设计模式 Singleton是一种创建型模式,指某个类采用Singleton ...

  7. 探讨一下Java单例设计模式

    所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在.就像是Java Web中的application,也就是提供了一个全局变量,用处相当广泛,比如保存全局数据,实现全局性的操作等. 1. ...

  8. java单例支持高并发

    单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频繁,对于一些大型的对象,这是一笔 ...

  9. java单例类/

    java单例类  一个类只能创建一个实例,那么这个类就是一个单例类 可以重写toString方法 输出想要输出的内容 可以重写equcal来比较想要比较的内容是否相等 对于final修饰的成员变量 一 ...

随机推荐

  1. Django之跨域请求同源策略

    同源策略: 首先基于安全的原因,浏览器是存在同源策略这个机制的,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. 而如果我们要跳过这个策略,也就是说非要跨域请求,那么就需要通过 ...

  2. 深度学习之 TensorFlow(五):mnist 的 Alexnet 实现

    尝试用 Alexnet 来构建一个网络模型,并使用 mnist 数据查看训练结果. 我们将代码实现分为三个过程,加载数据.定义网络模型.训练数据和评估模型. 实现代码如下: #-*- coding:u ...

  3. [AGC006] D - Median Pyramid Hard 二分

    Description ​ 现在有一个NN层的方块金字塔,从最顶层到最底层分别标号为1...N1...N. ​ 第ii层恰好有2i−12i−1个方块,且每一层的中心都是对齐的. 这是一个N=4N=4的 ...

  4. python中文件路径的问题

    慎用中文路径!慎重中文路径!!慎用中文路径!!! good = np.loadtxt(u'D:/feiq/feiq/Recv Files/Recv Files/LOS 数据集/good_user2', ...

  5. [CQOI2006]凸多边形(半平面交)

    很明显是一道半平面交的题. 先说一下半平面交的步骤: 1.用点向法(点+向量)表示直线 2.极角排序,若极角相同,按相对位置排序. 3.去重,极角相同的保留更优的 4.枚举边维护双端队列 5.求答案 ...

  6. 前端模块开发的基础代码,兼容amd,cmd标准,也可以直接使用window来引用对象

    (function(global, factory) { if (typeof define === 'function' && define.amd) { define(functi ...

  7. leetcode-229.求众数(二)

     题目: 给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素. 说明: 要求算法的时间复杂度为O(n),空间复杂度为O(1). 示例 1: 输入: [3,2,3] 输出: [3 ...

  8. What Goes Up UVA - 481 LIS+打印路径 【模板】

    打印严格上升子序列: #include<iostream> #include<cstdio> #include<algorithm> #include<cst ...

  9. 【转】nginx在Windows系统启动不了

    这几天用到Nginx,第一次是win7系统下部署,一次性成功,第二次在win10系统下,部署失败. 出现的情况: 打开Nginx.exe,界面一闪而过,而且进程里面搜不到Nginx. 1.端口占用问题 ...

  10. python学习之路---day02

    一:while循环 while 条件语句 #如果条件成立则执行下面的循环语句 循环语句 eg1:1+2+3+4+5......+100=? num=0 #给num和sum赋初值 sum=0 while ...