原创文章,转载请注明:转载自 周岳飞博客(http://zhou-yuefei.iteye.com/)
首先回顾一下单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
首先我们写一个简单的单例类:
- public class Singleton {
- /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
- private static Singleton instance = null;
- /* 私有构造方法,防止被实例化 */
- private Singleton() {
- }
- /* 静态工程方法,创建实例 */
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
- public Object readResolve() {
- return instance;
- }
- }
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:
- public static synchronized Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (instance) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
原创文章,转载请注明:转载自 周岳飞博客(http://zhou-yuefei.iteye.com/)
不幸的是,这个代码在优化编译器或共享内存的多处理器下就会有问题。
第一个问题:
同步代码块中对象的初始化,和赋值操作的顺序是未知的(不清楚的可以看这篇文章http://zhou-yuefei.iteye.com/blog/2219228),也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,(Singleton的构造方法未执行,导致其中的字段值不是构造方法的中指定的值)
// (Still) Broken multithreaded version
// "Double-Checked Locking" idiom class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) { Helper h; synchronized(this) { h = helper; if (h == null) synchronized (this) { h = new Helper(); } // release inner synchronization lock helper = h; } } return helper; } // other functions and members... }
此代码将Helper对象构建放到内部的同步块中,。在这里,直观的想法是,在离开同步代码块的时候,有一个内存屏障,并应防止Helper对象初始化和变量赋值操作的重排序。
然而,sychronized 操作只保证在释放锁前,同步代码块中的操作被执行,并不保证已经完成,所以,上面的问题还会出现
还有一个问题:
关于java内存可见性,每个线程都要自己的本地内存,每个线程对变量的更新操作,其他线程不能立即感知到,线程A将helper的值更新了,可是线程B检查还是null,从而又创建了个对象,
解决办法加上volatile 关键字
private volatile Helper helper = null;
原创文章,转载请注明:转载自 周岳飞博客(http://zhou-yuefei.iteye.com/)
相关推荐
- **并发问题**:在多线程环境中,单例模式可能会导致线程安全问题,需要额外处理同步机制。 #### 五、实例解析 在给定的内容中提到的`Martin`类就是一个典型的单例模式实现案例。它通过将构造器私有化以及提供一...
本篇将深入解析单例设计模式的四种实现方式,并通过具体的案例进行详细分析。 一、懒汉式(线程不安全) 懒汉式单例的特点是在类被加载时并不创建实例,而是首次调用getInstance()方法时才创建。这种实现方式简单...
`DateFormat`可以使用`ThreadLocal`来创建线程本地实例,确保每个线程都有自己独立的格式化对象,避免线程安全问题。示例代码如下: ```java ThreadLocal<DateFormat> threadLocalDateFormat = new ThreadLocal() ...
### 深入解析volatile关键字:保障多线程下的数据一致性与可见性 #### 一、volatile的基本概念 ##### 1.1 可见性 在并发编程领域中,可见性是一个极为重要的概念,它指的是当一个线程修改了一个共享变量的值后,...
《深入解析:单例模式在实现“飞机大战”游戏中的应用》 在编程领域,设计模式是一种被广泛接受和使用的解决常见问题的方案,其中单例模式尤其受到开发者们的青睐。在C#编程中,单例模式是创建型设计模式之一,它...
单例模式在多线程环境中需要考虑线程安全问题。懒汉式单例在多线程环境下可能会出现多个实例,因此需要使用双重检查锁定(Double-Check Locking)或者静态内部类等线程安全的方式来实现。另外,饿汉式单例在类加载时...
- **获取单例实例的方法**:一般有两种实现方式,懒汉式(延迟初始化,线程不安全)和饿汉式(提前初始化,线程安全)。为了在多线程环境下保证线程安全,还可以采用双检锁/双重校验锁定(DCL,Double-Check ...
本文将详细解析Java语言中的五种单例模式实现方式,并通过源代码和注释帮助读者深入理解。 1. 饿汉式(静态常量) 饿汉式单例在类加载时就完成了初始化,因此是线程安全的。这种方式简单直接,但缺点是如果单例类很...
在多线程环境下,为了提高执行效率,编译器或处理器可能会对指令进行重排序,但这种优化可能导致线程安全问题。volatile通过添加内存屏障来防止指令重排序,确保了对volatile变量的读写操作不会与其他指令交错,从而...
Java中使用双重检查锁定(Double-Check Locking)或静态内部类方式实现线程安全的单例。 4. 状态模式:用于管理对象的状态,特别是在多线程环境下的状态转换。例如,`java.util.concurrent.atomic`包中的原子类,如...
常见的线程安全单例实现有懒汉式(双重检查锁定DCL)和饿汉式。DCL使用`synchronized`关键字保证线程安全,同时避免了不必要的同步开销。 4. 管道通信模式: Java的`PipedInputStream`和`PipedOutputStream`可以...
单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 ...
Java多线程是Java编程中不可或...以上只是Java多线程面试中的一部分问题,实际面试可能涵盖更广泛的议题,如线程安全、死锁、线程池、并发工具类等。深入理解和熟练应用这些概念对于成为一名优秀的Java开发者至关重要。
《深入解析Spring等框架源码》是一份涵盖了Java开发领域中关键框架源码分析的教程资源,其中包含Spring、Netty以及MyBatis等核心框架的深度解析。这份压缩包旨在帮助开发者更深入地理解这些框架的工作原理,提升技术...
- **单例模式**:确保一个类只有一个实例,尤其在多线程环境中,需要考虑线程安全的单例实现。 - **生产者-消费者模式**:利用BlockingQueue实现线程间的异步通信和任务分发。 - **线程局部变量**(ThreadLocal)...
本项目可能运用这些工具确保线程安全,比如在写入数据到文件或数据库时。 4. **异步编程**:`async/await`关键字是C#中实现异步操作的重要手段,它能避免线程阻塞,提高程序响应性。在数据抓取过程中,可能用到了...
作者阐述了`volatile`关键字的语义,如何保证并发环境下的数据一致性,并通过实例演示了如何在单例设计模式中应用`volatile`,以实现线程安全的单例。 第四部分是多线程设计架构模式,涵盖了多种用于提高并发性能...