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/

最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注個人公衆號:「程序那些事」,懂技術,更懂你!

相關文章
相關標籤/搜索