java:基于volatile和Thread Local Storage的双重检查锁定实现延迟初始化
版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/78519756
总在同一个地方栽坑里是不可原谅的,以本文做个记录,以防下次再犯。 下面这段很简单的基于双重检查锁定(Double-checked locking)实现的延迟初始化(Lazy initialization)代码,还是让spotbugs找出了问题(感谢spotbugs)。 原因很简单,这种模式在java下无效,因为filedNames 变量不是线程可见的,具体原因涉及到java内存模型,网上已经有文章很深入的介绍,参见本文末尾的参考资料4
private List<String> filedNames = null;
public List<String> getFieldNames() {
// Double-checked locking
if(null == filedNames){
synchronized(this){
if(null == filedNames){
filedNames = doGetFieldNames();
}
}
}
return filedNames;
}
#解决方案1
把 fieldName 声明为volatile型,其他代码不变。 注意: 这个解决方案需要JDK5或更高版本(因为从JDK5开始使用新的JSR-133内存模型规范,这个规范增强了volatile的语义)。关于volatile关键字的含义参见参考资料。
/** JDK5 required */
private volatile List<String> filedNames = null;
public List<String> getFieldNames() {
// Double-checked locking
if(null == filedNames){
synchronized(this){
if(null == filedNames){
filedNames = doGetFieldNames();
}
}
}
return filedNames;
}
#解决方案2 基于线程本地存储TLS(Thread Local Storage)实现双重检查锁定,这个方法也是 [《The “Double-Checked Locking is Broken” Declaration》][1]论文中提出的方案之一。 这个方案不需要volatile关键字支持当然也就没有JDK5以上版本的要求,但是略复杂,多了一个ThreadLocal变量,并且分成了两个方法。
private volatile List<String> filedNames = null;
/** 如果perThreadInstance.get() 返回一个非null值,说明 filedNames 已经被初始化过了 */
@SuppressWarnings("rawtypes")
private final ThreadLocal perThreadInstance = new ThreadLocal();
public List<String> getFieldNames() {
this.assertJavaBean();
if(null == perThreadInstance.get()){
initFieldNames();
}
return filedNames;
}
/** 同步初始化filedNames变量 */
@SuppressWarnings({ "unchecked" })
private void initFieldNames() {
synchronized(this){
if(null == filedNames){
filedNames = doGetFieldNames();
}
}
// 给perThreadInstance设置一个非null值
perThreadInstance.set(perThreadInstance);
}
#通用化封装 说话仅仅一个延迟初始化就整出这么多问题,搞出这么多代码,虽然问题解决了,但对于我这个懒人来说实在太复杂了,如果项目中还有多个地方要用到延迟初始化,每个都要这么写代码实在是一件非常痛苦的事儿。 既然原理搞明白了,那么把这两种延迟初始化的解决方案用用泛型类封装一下不就可以复用了么?
于是我很快把上面的代码做了封装,顶层是接口类ILazyInitVariable,只有一个接口方法get()
,接下来是个中间BaseLazyVar抽象类定义一个doGet()
方法用于具体的初始化,BaseTls和BaseVolatile分别是基于前述方案2和方案1的具体实现类(也是抽象类,具体doGet()
方法还是需要子类来完成)
完整代码如下
ILazyInitVariable.java
接口定义 ILazyInitVariable.java,中间抽象类BaseLazyVar也在其中
package gu.simplemq;
/**
* 延迟初始化(Lazy initialization)变量封装接口
* @author guyadong
*
* @param <T> 延迟变量类型
*/
public interface ILazyInitVariable<T> {
public static abstract class BaseLazyVar<T> implements ILazyInitVariable<T>{
/**
* 返回 T 实例
* @return
*/
abstract protected T doGet() ;
}
/**
* 返回延迟初始化的 T 实例
* @return
*/
public T get();
}
##BaseVolatile.java
package gu.simplemq;
/**
* 基于volatile的双重检查锁定实现{@link ILazyInitVariable}的抽象类<br>
* 要求 JDK5 以上版本
* @author guyadong
*
* @param <T> variable type
*/
public abstract class BaseVolatile<T> extends ILazyInitVariable.BaseLazyVar<T>{
private volatile T var = null;
public BaseVolatile() {
}
@Override
public T get() {
// Double-checked locking
if(null == var){
synchronized(this){
if(null == var){
var = doGet();
}
}
}
return var;
}
}
##BaseTls.java
package gu.simplemq;
/**
* 基于Thread Local Storage的双重检查锁定实现{@link ILazyInitVariable}的抽象类<br>
* @author guyadong
*
* @param <T> variable type
*/
public abstract class BaseTls<T> extends ILazyInitVariable.BaseLazyVar<T> {
/**
* If perThreadInstance.get() returns a non-null value, this thread has done
* synchronization needed to see initialization of helper
*/
@SuppressWarnings("rawtypes")
private final ThreadLocal perThreadInstance = new ThreadLocal();
private T var = null;
public BaseTls() {
}
@Override
public T get() {
if (null == perThreadInstance.get()) {
initFieldNames();
}
return var;
}
@SuppressWarnings({ "unchecked" })
private void initFieldNames() {
synchronized (this) {
if (null == var) {
var = doGet();
}
}
// Any non-null value would do as the argument here
perThreadInstance.set(perThreadInstance);
}
}
##使用示例
有了通用化封装,以用BaseVolatile
为例
本文最开始的延迟初始化代码就很简单了:
// filedNames 定义为ILazyInitVariable接口实例,并用BaseVolatile类实例化
private final ILazyInitVariable<List<String>>filedNames = new BaseVolatile<List<String>>(){
@Override
protected List<String> doGet() {
// 调用初始化方法
return doGetFieldNames();
}};
public List<String> getFieldNames() {
return filedNames.get();
}
#参考资料
- [《The “Double-Checked Locking is Broken” Declaration》][1]
- [《Lazy initialization》][2]
- [《Double-checked locking》][3]
- [《双重检查锁定与延迟初始化》][4]
- [《双重检查锁定失败可能性——参照《The “Double-Checked Locking is Broken” Declaration》》][5]
- [《Java中Volatile关键字详解》][6] [1]:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html [2]:https://en.wikipedia.org/wiki/Lazy_initialization [3]:https://en.wikipedia.org/wiki/Double-checked_locking [4]:http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization [5]:http://freish.iteye.com/blog/1008304 [6]:https://www.cnblogs.com/zhengbin/p/5654805.html
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android 10(Q)/11(R) 分区存储适配
- Usual*** CMS 8.0代码审计
- 由一条like语句引发的SQL注入新玩法
- 《黑神话:悟空》B站弹幕、知乎回答分析
- 12岁小读者使用Python暴力破解Wi-Fi密码
- 深度学习入门Fast.ai 2.0上线!自带中文字幕,所有笔记、资源全部免费!
- 七夕节脱单“神助攻”!AI教你写情话
- Python初学者请注意!别这样直接运行python命令,否则电脑等于“裸奔”
- 一篇文章构建你的 Node.js 知识体系
- MySQL:The CHAR and VARCHAR Types
- 更新一个10年有效期的 Kubernetes 证书
- 哇,ElasticSearch多字段权重排序居然可以这么玩
- Python 自动化,Appium 凭什么使用 UiAutomator2?
- 我用几行 Python 自动化脚本完美解决掉了小姐姐的微信焦虑感
- 【设计模式】692- TypeScript 设计模式之发布-订阅模式