Java多線程系列八——volatile和ThreadLocal

來源:互聯網
上載者:User

標籤:desc   重新整理   static   single   ide   抽象   java多線程   throws   參考   

參考資料:

http://ifeve.com/java-memory-model-4/

http://www.infoq.com/cn/articles/java-memory-model-1

http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

https://en.wikipedia.org/wiki/Singleton_pattern#Java_5_solution

https://www.ibm.com/developerworks/java/library/j-jtp06197/

1. volatile

final class Singleton {    private static Singleton instance = null;    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

 以上代碼嘗試實現單例模式,但存在嚴重的安全執行緒風險。Java Memory Model定義了線程和主記憶體之間的抽象關係:線程之間的共用變數儲存在主記憶體(main memory)中,每個線程都有一個私人的本地記憶體(local memory),本地記憶體中儲存了該線程以讀/寫共用變數的副本。假設Thread1/Thread2並發,instance為它們的共用變數,Thread1與Thread2之間通訊必須要經曆下面2個步驟:

  • Thread1把本地記憶體更新過的instance重新整理到主記憶體中去
  • Thread2到主記憶體中去讀取Thread1之前已更新過的instance

那麼可能的情境之一——Thread1執行完instance = new Singleton(),但重新整理到主記憶體前Thread2的instance == null仍然成立,於是再次執行instance = new Singleton(),這時兩個線程得到了兩個不同的對象,與預期不符。

final class Singleton {    private static Singleton instance = null;    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

 加入鎖和雙重校正後,仍然存在風險,因為為了提高效能,編譯器和處理器常常會對指令做重排序,以Singleton instance = new Singleton()為例,它包含了三個指令:

  • ①為instance分配記憶體
  • ②調用Singleton構造方法
  • ③把instance指向分配的記憶體位址

三個指令執行順序可能是①②③或①③②,在③執行之後,instance==null將不再成立。可能的情境——假設Thread1/Thread2並發,Thread1執行了除②以外的指令,Thread2的instance==null不成立,雖然得到了記憶體位址,但由於未調用構造方法而報錯。

final class Singleton {    private static volatile Singleton instance = null;    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

為instance變數加上volatile關鍵字徹底解決問題。volatile的特性:

  • volatile的變數修改後將立即重新整理到主記憶體,其他線程即可讀取到新值
  • 編譯器利用記憶體屏障的概念禁止上述三條指令的重排序,只允許①②③的執行順序

由於以上特性使volatile極適用於修飾多線程環境下的狀態標識。

2. ThreadLocal

當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的線程提供獨立的變數副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

以非安全執行緒的SimpleDateFormat類為例,在並行執行階段會出錯,但使用ThreadLocal維護則可以完美避免此問題

import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;/** * @Description: 測試ThreadLocal */public class ThreadLocalTest {    private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");    private static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {        public DateFormat initialValue() {            return new SimpleDateFormat("yyyy-MM-dd");        }    };    public static void main(String[] args) throws InterruptedException {        String date = "2017-07-06";        testDateFormat(date);        testThreadLocal(date);    }    private static void testDateFormat(String date) throws InterruptedException {        multilpleThreadExecute(new Runnable() {            @Override            public void run() {                try {                    System.out.println(df.parse(date));                } catch (ParseException e) {                }            }        });    }    private static void testThreadLocal(String date) throws InterruptedException {        multilpleThreadExecute(new Runnable() {            @Override            public void run() {                try {                    System.out.println(DATE_FORMAT.get().parse(date));                } catch (ParseException e) {                }            }        });    }    private static void multilpleThreadExecute(Runnable runnable) throws InterruptedException {        ExecutorService executorService = Executors.newCachedThreadPool();        for (int i = 0; i < 10; i++) {            executorService.execute(runnable);        }        executorService.shutdown();        executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);    }}

 

Java多線程系列八——volatile和ThreadLocal

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.