在《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位操作 ...
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位操作 ...
(1)确保某个计算在其需要的所有资源都被初始化后才能继续执行 (2)确保某个服务在其所依赖的所有其他服务都已经启动之后才启动 (3)等待知道某个操作的所有参与者都就绪再继续执行 ...
在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...
在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...
16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”) 16.3.6 迫切左外连接检索(fetch属性为“join”) 16.4 多对一和一对一关联的检索策略 16.4.1 迫切左外连接检索(fetch...
16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”) 16.3.6 迫切左外连接检索(fetch属性为“join”) 16.4 多对一和一对一关联的检索策略 16.4.1 迫切左外连接检索(fetch...
16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”) 16.3.6 迫切左外连接检索(fetch属性为“join”) 16.4 多对一和一对一关联的检索策略 16.4.1 迫切左外连接检索(fetch...
16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”) 16.3.6 迫切左外连接检索(fetch属性为“join”) 16.4 多对一和一对一关联的检索策略 16.4.1 迫切左外连接检索(fetch...
/ 170 第7章 虚拟机类加载机制 / 171 7.1 概述 / 171 7.2 类加载的时机 / 172 7.3 类加载的过程 / 176 7.3.1 加载 / 176 7.3.2 验证 / 178 7.3.3 准备 / 181 7.3.4 解析 / 182 7.3.5 初始化 / 186 7.4 类...
7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 破坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧结构 8.2.1 局部变量表 8.2.2 操作数栈 ...
类什么时候才被初始化: 58 类的初始化步骤: 59 【*JVM】什么是JVM线程死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息? 59 【*JVM】查看jvm虚拟机里面堆、线程的...
第16章 多线程——Java中的并发协作 343 16.1 线程的基本知识 343 16.1.1 多线程编程的意义 343 16.1.2 定义自己的线程 344 16.1.3 创建线程对象 345 16.1.4 启动线程 347 16.1.5 同时使用多个线程 ...
一个可靠、灵活、快速和强大的下载引擎。 简单的用例,例如启动和取消、下载... 您只需创建自己的 DownloadMgrInitialParams.InitCustomMaker 并将自定义组件放入其中,然后用它“初始化” FileDownloader 即可验证它。
在 ETL 工作流中,id 通常对于清理和专门化您的数据以满足低延迟应用程序需求很有意义。 就像您的原始数据将移动到 ORC 文件格式的通用数据一样,您的应用程序数据可以以 Phoenix 表结尾,以实现极低的延迟和并发...
13.5.1 初始化Model192 13.5.2 更新Model197 13.6 ServletInvocableHandlerMethod199 13.6.1 HandlerMethod199 13.6.2 InvocableHandlerMethod203 13.6.3 ServletInvocableHandler-Method205 13.7 ...
状态是编程中经常碰到的实例,将状态对象化,设立状态变换器,便可在状态中轻松切换. 设计模式之 Memento(注释状态?) 很简单一个模式,就是在内存中保留原来数据的拷贝. 设计模式之 Interpreter(解释器) 主要用来对...