java安全编码指南之:锁的双重检测
简介
双重检测锁定模式是一种设计模式,我们通过首次检测锁定条件而不是实际获得锁从而减少获取锁的开销。
双重检查锁定模式用法通常用于实现执行延迟初始化的单例工厂模式。延迟初始化推迟了成员字段或成员字段引用的对象的构造,直到实际需要才真正的创建。
但是我们需要非常小心的使用双重检测模式,以避免发送错误。
单例模式的延迟加载
先看一个在单线程正常工作的单例模式:
public class Book {
private static Book book;
public static Book getBook(){
if(book==null){
book = new Book();
}
return book;
}
}
上面的类中定义了一个getBook方法来返回一个新的book对象,返回对象之前,我们先判断了book是否为空,如果不为空的话就new一个book对象。
初看起来,好像没什么问题,我们仔细考虑一下:
book=new Book()其实一个复杂的命令,并不是原子性操作。它大概可以分解为1.分配内存,2.实例化对象,3.将对象和内存地址建立关联。
在多线程环境中,因为重排序的影响,我们可能的到意向不到的结果。
最简单的办法就是加上synchronized关键字:
public class Book {
private static Book book;
public synchronized static Book getBook(){
if(book==null){
book = new Book();
}
return book;
}
}
double check模式
如果要使用double check模式该怎么做呢?
public class BookDLC {
private static BookDLC bookDLC;
public static BookDLC getBookDLC(){
if(bookDLC == null ){
synchronized (BookDLC.class){
if(bookDLC ==null){
bookDLC=new BookDLC();
}
}
}
return bookDLC;
}
}
我们先判断bookDLC是否为空,如果为空,说明需要实例化一个新的对象,这时候我们锁住BookDLC.class,然后再进行一次为空判断,如果这次不为空,则进行初始化。
那么上的代码有没有问题呢?
有,bookDLC虽然是一个static变量,但是因为CPU缓存的原因,我们并不能够保证当前线程被赋值之后的bookDLC,立马对其他线程可见。
所以我们需要将bookDLC定义为volatile,如下所示:
public class BookDLC {
private volatile static BookDLC bookDLC;
public static BookDLC getBookDLC(){
if(bookDLC == null ){
synchronized (BookDLC.class){
if(bookDLC ==null){
bookDLC=new BookDLC();
}
}
}
return bookDLC;
}
}
静态域的实现
public class BookStatic {
private static BookStatic bookStatic= new BookStatic();
public static BookStatic getBookStatic(){
return bookStatic;
}
}
JVM在类被加载之后和被线程使用之前,会进行静态初始化,而在这个初始化阶段将会获得一个锁,从而保证在静态初始化阶段内存写入操作将对所有的线程可见。
上面的例子定义了static变量,在静态初始化阶段将会被实例化。这种方式叫做提前初始化。
下面我们再看一个延迟初始化占位类的模式:
public class BookStaticLazy {
private static class BookStaticHolder{
private static BookStaticLazy bookStatic= new BookStaticLazy();
}
public static BookStaticLazy getBookStatic(){
return BookStaticHolder.bookStatic;
}
}
上面的类中,只有在调用getBookStatic方法的时候才会去初始化类。
ThreadLocal版本
我们知道ThreadLocal就是Thread的本地变量,它实际上是对Thread中的成员变量ThreadLocal.ThreadLocalMap的封装。
所有的ThreadLocal中存放的数据实际上都存储在当前线程的成员变量ThreadLocal.ThreadLocalMap中。
如果使用ThreadLocal,我们可以先判断当前线程的ThreadLocal中有没有,没有的话再去创建。
如下所示:
public class BookThreadLocal {
private static final ThreadLocal<BookThreadLocal> perThreadInstance =
new ThreadLocal<>();
private static BookThreadLocal bookThreadLocal;
public static BookThreadLocal getBook(){
if (perThreadInstance.get() == null) {
createBook();
}
return bookThreadLocal;
}
private static synchronized void createBook(){
if (bookThreadLocal == null) {
bookThreadLocal = new BookThreadLocal();
}
perThreadInstance.set(bookThreadLocal);
}
}
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-double-check-lock/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
相关文章
- java replaceAll() 关于其参数正则匹配
- 如何创建并运行java线程
- JAVA 报错exe4j中this executable was created with an evaluation 怎么办
- java错误:The superclass "javax.servlet.http.HttpServlet" was not found on the Java Bu
- JAVA学习(一):Java介绍及其平台、开发环境的配置与搭建
- Java实现 LeetCode 838 推多米诺(暴力模拟)
- Java实现 LeetCode 718 最长重复子数组(动态规划)
- Java实现固定长度得01子串
- Java实现 LeetCode 118 杨辉三角
- Java实现 基础算法 求100以内的质数
- java实现第四届蓝桥杯金蝉素数
- (Java实现) 活动选择
- Java中如何输出对勾,ASCII编码与字符串相互转换
- java 11 标准Java异步HTTP客户端
- java多线程 -- volatile 关键字 内存 可见性
- 【JAVA】java中char类型数组用数组名打印结果不是地址值而是数组内容
- 【JAVA】Java 异常中e的getMessage()和toString()方法的异同
- 【JAVA】 01-Java基础知识
- java base64编码和解码
- Eclipse 报 “Exception in thread "main" java.lang.OutOfMemoryError: Java heap space ”错误的解决办法
- 【JAVA】java编译错误:编码UTF8/GBK的不可映射字符
- 【JAVA】 03-Java中的异常和包的使用
- 【JAVA】 02-Java对象细节
- Java每日一练(20230324)
- Java每日一练(20230311)
- Atitit.Java exe bat 作为windows系统服务程序运行
- 华为OD机试 - 最佳对手(Java & JS & Python)
- 华为OD机试 - 高效的任务规划(Java & JS & Python)
- How to improve Java's I/O performance( 提升 java i/o 性能)
- 【java】Java中-> 是什么意思?
- Java Vertor详细介绍和使用示例
- JAVA语言之Java 中不同的并行实现的性能比较
- JAVA语言之Java 中不同的并行实现的性能比较
- 【java提高】---java反射机制
- JAVA开发讲义(二)-Java程序设计之数据之谜二