在《java并发编程实战》这本书的第十六章中讲到不安全的发布时,给了一个不安全的延迟初始化示例:
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
}
static class Resource {
}
}
一般情况下,估计大家都会这么写代码,在非并发环境中,这个getInstance方法会工作的很好,但是放到并发环境中,问题就来了,比如竞态条件,但这里还存在另外一个问题,即另一个线程可能会看到对部分构造的Resource实例的引用。
简单点来说,就是线程A在访问getInstance方法时,发现resource为null,于是就resource设置为一个新实例,在这个过程中,线程B也调用getInstance方法,发现resource不为空,因此就直接使用这个resource实例了。看起来貌似没有问题,但是因为线程A实例化resource的操作和线程B读取resource实例的操作之间不存在Happens-Before关系,所以,在线程B使用resource实例时,resource实例也许还未构造完成,这就导致了线程B看到的resource实例不正确的状态。
解决这个问题的一个简单办法就是使用同步,这也是我们经常会使用的办法:
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
static class Resource {
}
}
这种办法够简单直接,但是在getInstance方法被频繁调用的时候,还是会存在激烈的竞争。书中还给出了另外一种办法,就是不采用延迟初始化,也就是提前初始化,在定义resource时候,就实例化它:
public class EagerInitialization {
private static Resource resource = new Resource();
public static Resource getResource() {
return resource;
}
static class Resource {
}
}
这种办法在日常开发中也会采用到。但是这种办法为什么是线程安全的呢?这涉及到JVM在类的初始化阶段给出的线程安全性保证。因为JVM在类初始化阶段,会获取一个锁,并且每个线程都会至少获取一次这个锁以确保这个类已经加载,在静态初始化期间,内存的写入操作自动对所有线程可见,而resource的初始化就是属于静态初始化。因此,在构造期间或者被引用时,静态初始化的对象都不需要显式的同步,但是这个规则只适用于在构造时的状态,如果对象可变,那么在其它地方对该对象的访问还是需要使用同步来确保对对象的修改操作是可见的。
下面再来看看另一种解决办法,书中称之为延迟初始化占位类模式,我认为这个方法很巧妙:
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
这种方法就是基于上述JVM在类的初始化阶段给出的线程安全性保证,将resource的实例化操作放置到一个静态内部类中,在第一次调用getResource方法时,JVM才会去加载ResourceHelper类,同时初始化resource实例,因此,即使我们不采取任何同步策略,getResource方法也是线程安全的。
后面还有讲到基于双重检查锁(DCL)的方式来实现,但是这种方法属于糟糕的方法,这里就不过多描述了,示例代码如下:
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
static class Resource {
}
}
分享到:
相关推荐
2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁机制 2.3.1 内置锁 2.3.2 重入 2.4 用锁来保护状态 2.5 活跃性与性能 第3章 对象的共享 3.1 可见性 3.1.1 失效数据 3.1.2 非原子的64位操作 ...
- 延迟初始化(Lazy Initialization):延迟对象的初始化直到它第一次被使用,可以提高性能,但也需要同步控制。 - 线程封闭(Thread Confinement):一种确保线程安全的策略,通常通过ThreadLocal等机制实现。 - 不...
8. **延迟初始化与单例模式**:书中探讨了在并发环境下的延迟初始化和单例模式实现,比如双检锁(DCL)和静态内部类等实现方式。 9. **并发性能分析**:并发程序的性能分析和调优是另一个重要主题。书中提供了工具和...
2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁机制 2.3.1 内置锁 2.3.2 重入 2.4 用锁来保护状态 2.5 活跃性与性能 第3章 对象的共享 3.1 可见性 3.1.1 失效数据 3.1.2 非原子的64位操作 ...
7. **并发设计模式**:生产者消费者模型、读写锁策略、双检锁/双重校验锁(DCL)、延迟初始化等是常见的并发设计模式。理解和应用这些模式能解决很多并发编程中的问题。 8. **并发异常处理**:在并发环境中,必须...
8. 复合操作和延迟初始化的线程安全性:对于涉及多个可变字段的复合操作,需要以原子方式进行更新以保证线程安全。延迟初始化涉及的线程安全问题,可以使用双重检查锁定模式来解决。 9. Java内置锁的重入性:内置锁...
《Java并发程序设计教程》是一份详尽的指南,旨在帮助开发者掌握Java中的并发编程技巧。这份教程由温绍锦(昵称:温少)编写,涵盖了从基础到高级的各种并发概念和技术,对于希望深入理解并应用Java多线程机制的开发...
### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 ...以上是Java并发编程实践笔记中总结的关键知识点,涵盖了从基本概念到高级技术的应用,旨在帮助开发者构建高效、可靠的多线程应用。
19. 保证共享变量的发布是安全的,可以通过静态初始化器初始化对象,或者将对象申明为volatile或使用AtomicReference。 20. 设计线程安全的类,应该包括哪些是可变共享变量,哪些是不可变的变量,以及指定一个管理...
以下是一些在Java并发编程中常见的概念: 1. **Java内存模型 (Java Memory Model)**: - **定义**:Java SE 5 (JSR133) 中定义的Java内存模型 (JMM) 旨在为编写并发代码的Java程序员提供一个一致的平台。它确保了...
总结来说,`volatile`关键字是Java并发编程中一个非常重要的概念。它不仅解决了多线程环境下的可见性问题,还能够在一定程度上保证有序性。然而,它并不保证复合操作的原子性,因此在设计并发程序时,还需要根据具体...
Java并发编程规范是Java开发中不可或缺的一部分,遵循这些规范能够帮助我们编写出更稳定、高效且易于维护的多线程程序。以下是对给定文件中提及的一些关键知识点的详细解释: 1. **指定线程名称**:在创建线程或...
(1)确保某个计算在其需要的所有资源都被初始化后才能继续执行 (2)确保某个服务在其所依赖的所有其他服务都已经启动之后才启动 (3)等待知道某个操作的所有参与者都就绪再继续执行 ...
2. 并发编程模式:如双检锁、延迟初始化等,优化并发性能,减少锁的使用。 通过深入学习和实践上述知识点,开发者可以有效地应对高并发场景,构建出高效稳定的Java应用程序。"java并发编程-从入门到精通.pdf"这份...
### Java并发程序设计知识点详解 #### 一、使用线程的经验 在Java中,线程是并发编程的基础单元。为了更好地管理和识别线程,给线程命名是非常重要的实践。 1. **设置名称**: - **重要性**:命名线程可以帮助...
通过学习《JAVA并发编程实践.pdf》,你将能够掌握如何使用Java提供的并发工具,编写出高性能、低延迟的多线程程序,同时避免并发编程中的陷阱和错误。 总之,Java设计模式和并发编程是Java开发的两大基石,它们...
- 在某些初始化步骤完成后通知其他等待线程。 2. **CyclicBarrier**: - **定义**:`CyclicBarrier`是一个让一组线程等待到达某个公共屏障点的同步工具类。 - **使用场景**: - 在多个线程之间安排一个同步点。...