java安全编码指南之:锁的双重检测

简介

双重检测锁定模式是一种设计模式,咱们经过首次检测锁定条件而不是实际得到锁从而减小获取锁的开销。java

双重检查锁定模式用法一般用于实现执行延迟初始化的单例工厂模式。延迟初始化推迟了成员字段或成员字段引用的对象的构造,直到实际须要才真正的建立。git

可是咱们须要很是当心的使用双重检测模式,以免发送错误。github

单例模式的延迟加载

先看一个在单线程正常工做的单例模式:设计模式

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关键字:code

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/

最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注个人公众号:「程序那些事」,懂技术,更懂你!

相关文章
相关标签/搜索