定义:

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。

下面通过代码分析下java中,各种单例模式写法的优缺点。

1、饿汉模式

示例1.1

public class Singleton {private Singleton() {}private static Object INSTANCE = new Object();public static Object getInstance() {return INSTANCE;}
}

在类生命周期的【初始化】阶段进行生成单例对象(类的初始化阶段会对静态变量赋值),当执行类初始化的阶段是需要先获得锁才能进行初始化操作,而且一个class类只进行初始化一次。类初始化阶段是线程安全的,JVM保证类初始化只执行一次。这样可以确保只生成一个对象。

类声明周期分为:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸御(Unloading)。
类的生命周期不明白的请查看:JVM 类加载机制深入浅出

类加载后不一定马上执行初始化阶段。当遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。

  1. new 创建对象操作
  2. getstatic 访问类的静态变量操作
  3. putstatic 给类的静态变量赋值操作
  4. invokestatic 调用静态方法操作

这个饿汉模式中,不会出现new、invokestatic和putstatic指令,外面的类只能调用 getInstance()静态方法,由此推断,此单例模式也是延迟加载对象的,只有第一次调用getInstance()静态方法,才会触发他的初始化阶段,才会创建单例对象。

其实这个例子应该是懒汉模式,只有在第一次使用的时候才加载

下面这个【示例1.2】不是延迟加载单例对象

示例1.2

public class Singleton {private Singleton() {}private static  int count=0;private static Object INSTANCE = new Object();public static Object getInstance() {return INSTANCE;}
}

当程序先调用Singleton1中的count属性时(getstatic 或putstatic 指令),就会执行类的【初始化】阶段,会生成单例对象,而不是调用getInstance()静态方法才生成单例对象。

示例1.3 (静态内部类实现方式)

public class Singleton {private Singleton() {}private static  int count=0;private static class SingletonHolder{private static final Object INSTANCE = new Object();}public static Object getInstance(){return SingletonHolder.INSTANCE;}
}

使用内部类SingletonHolder来防止【示例1.2】出现的问题,防止其它的变量的干扰,导致提前触发类声明周期中的【初始化】阶段来创建INSTANCE 实例。
Effective Java中推荐的单例写法

2、懒汉模式

示例2.1

public class Singleton{private Singleton() {   }private static Object INSTANCE = null;public static Object getInstance() {if(INSTANCE == null){INSTANCE = new Object();}return INSTANCE;}
}

每次创建INSTANCE 的时候先判断是否null,如果为null则new一个,否则就直接返回INSTANCE 。当多线程工作的时候,如果有多个线程同时运行到if (INSTANCE == null),都判断为null,那么两个线程就各自会创建一个实例。这样就会创建多一个实例,这样就不是单例了。

下面的【示例2.2】加上synchronized 改进多线程并发引起的问题

示例2.2 (synchronized 实现方式)

public class Singleton {private Singleton() {   }private static Object INSTANCE = null;public synchronized static Object getInstance() {if(INSTANCE == null){INSTANCE = new Object();}return INSTANCE;}
}

虽然synchronized 能解决多线程同时并发引起的问题,但是每次访问该方法都需要获得锁,性能大大降低。其实只要创建INSTANCE 实例后就不需要加锁的,直接获取该对象就ok。

示例2.3 (双重检查实现方式)

public class Singleton {private Singleton() {   }private static Object INSTANCE = null;public static Object getInstance() {if(INSTANCE == null){synchronized(Singleton3.class){if(INSTANCE == null){INSTANCE = new Object();}}}return INSTANCE;}
}

这个版本的代码看起来有点复杂,注意其中有两次if (instance == null)的判断,这个叫做『双重检查 Double-Check』。

第一个if (instance == null),其实是为了解决【示例2.2】中的效率问题,只有instance为null的时候,才进入synchronized的代码段——这样在对象创建后就不会在进入同步代码块了。
第二个if (instance == null),则是跟【示例2.2】一样,是为了防止可能出现多个实例的情况。

从代码层面看似完美,效率问题也解决了。但实际还是有问题,在并发环境下可能会出现instance为null的情况。下面我们来分析下为什么会出现此问题。

原子操作

INSTANCE = new Object();不是原子操作。
在JVM中会拆分成3个步骤
1、分配对象的内存空间
2、初始化对象
3、设置INSTANCE 指向刚分配的内存地址

指令重排

指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
可以参考:java内存模型

【2、初始化对象和 3、设置INSTANCE 指向刚分配的内存地址】这两个操作可能发生重排序。
如下图:

从图中可以看出A2和A3的重排序,将导致线程
B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访
问到一个还未初始化的对象。

示例2.4 (基于volatile的解决方案)

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

声明对象的引用为volatile后,【2、初始化对象和 3、设置INSTANCE 指向刚分配的内存地址】之间的重排序,在多线程环境中将会被禁止。


从图表中可以看出volatile可以确保,volatile变量读写顺序,可以保证一个线程写volatile变量完成后(创建完对象后),其它线程才能读取该volatile变量,相当于给这个创建实例的构造上了一把锁。这样,在它的赋值完成之前,就不用会调用读操作。

示例2.5 (枚举实现方式)

public enum Singleton6 {INSTANCE;public String getInfo(String s){s = "hello " + s;System.out.println(s);return s;}public static void main(String[] args) {String s = INSTANCE.getInfo("aa");System.out.println(s);}
}

这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了序列化机制,绝对防止对此实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这中方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

本人简书blog地址:http://www.jianshu.com/u/1f0067e24ff8    
点击这里快速进入简书

多线程并发下的单例模式相关推荐

  1. OkHttp实现多线程断点续传下载,单例模式下多任务下载管理器,一起抛掉sp,sqlite的辅助吧

    丨版权说明 :<OkHttp实现多线程断点续传下载,单例模式下多任务下载管理器,一起抛掉sp,sqlite的辅助吧>于当前CSDN博客和乘月网属同一原创,转载请说明出处,谢谢. 最近项目需 ...

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

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

  3. [Java]Java的静态构造函数 多线程下安全的单例模式

    我也在看书的时候 看到C#的静态构造方法 也在想JAVA有没有同样的东西. 先来看一看为什么C#的静态构造方法为什么会存在 我们平常普通的做法是 public class Teacher {priva ...

  4. Java 多线程(四)—— 单例模式

    这篇博客介绍线程安全的应用--单例模式. 单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一 ...

  5. mat查找多线程并发下大对象导致的堆内存问题

    场景代码大概如下: 多线程并发请求接口,接口中的代码首先从guavacache.getIfAbsent()查找对象A,假设本地内存没有,代码又调用fetchFromRedisList获取数据,获取到数 ...

  6. 多线程并发下集合不安全类-ArrayList

    ArrayList集合在多线程并发操作下是不安全的 ArrayList集合在并发操作下会发生异常:故障现象java.util.ConcurrentModificationException 导致原因 ...

  7. Python多线程下实现单例模式,以及limit实例模式

    多线程环境下实现单例模式 下面介绍了两种Python实现单例模式的方法 1.重写__new__方法实现多线程情况下的单例模式 用new方法实现单例模式 import time, threadingcl ...

  8. 多线程下单例模式:懒加载(延迟加载)和即时加载

    在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用.这个机制在single-thr ...

  9. C++ 多线程下的单例模式

    /*代码中经常会使用到单例模式,单例模式就是隐藏构造函数,提供获取一个实例的静态方法.但是在多线程场景下,单例模式会有一些不同.例如Config类的instance方法如下*///获取一个实例(对外接 ...

最新文章

  1. 灵魂 36 问,让你快速熟悉一个系统
  2. java虚拟机 第二章Java内存区域与内存溢出异常
  3. [python] 使用scikit-learn工具计算文本TF-IDF值
  4. 安装telnet_Flask干货:Memcached缓存系统——Memcached的安装
  5. myelicpes怎么导入PHP项目,利用PHP执行SQL文件,将SQL文件导入到数据库
  6. 《Python Cookbook 3rd》笔记(1.8):字典运算
  7. Linux内存管理(最透彻的一篇)
  8. centos 7 firewalld 设置
  9. 带你了解Node.js包管理工具:包与NPM
  10. 微信又上线新功能,能让你更会聊天?
  11. PHP 变量 与 运算符
  12. html怎么用excel打开乱码,excel打开是乱码,详细教您excel打开是乱码怎么解决
  13. MIME 类型大全,你值得收藏
  14. 【详解】服务可用性详细说明及其解决方案
  15. python找不到模块pyodbc_Python:找不到pyodbc导入模块
  16. SWUSTOJ #67 学生成绩管理
  17. 有关2pc, 3pc,Tcc 的理解
  18. 为JBoot系统增加启动任务
  19. 蓝牙耳机什么牌子音质好听?蓝牙耳机音质排行榜
  20. 长波红外线灯的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告

热门文章

  1. 期末考试前的预习,科目:化工设备与反应器(4)
  2. java 100例(二)
  3. 图解Myeclipse 导入Java Web项目报错的解决办法听语音
  4. 语音合成:模拟最像人类声音的系统
  5. 上海人工智能实验室刘宇:从感知AI的发展理解决策AI的未来
  6. 2021年下半年,你还可以把论文投给这 9 个国际会议
  7. GPT v.s. 中国象棋:写过文章解过题,要不再来下盘棋?
  8. 一篇文章入门深度学习框架PyTorch
  9. 基于Anaconda安装GPU版PyTorch深度学习开发环境
  10. 【MyBatis使用】mapper.xml 调试时无法打印SQL的无奈解决方法分享(原因说明+举例)