Java ThreadLocal (Java程式碼實戰-006)

FrankYou發表於2018-05-28

ThreadLocal解決什麼問題

由於 ThreadLocal 支援範型,如 ThreadLocal< StringBuilder >,為表述方便,後文用 變數 代表 ThreadLocal 本身,而用 例項 代表具體型別(如 StringBuidler )的例項。 

不恰當的理解
寫這篇文章的一個原因在於,網上很多部落格關於 ThreadLocal 的適用場景以及解決的問題,描述的並不清楚,甚至是錯的。下面是常見的對於 ThreadLocal的介紹

ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路
ThreadLocal的目的是為了解決多執行緒訪問資源時的共享問題

還有很多文章在對比 ThreadLocal 與 synchronize 的異同。既然是作比較,那應該是認為這兩者解決相同或類似的問題。

上面的描述,問題在於,ThreadLocal 並不解決多執行緒 共享 變數的問題。既然變數不共享,那就更談不上同步的問題。

合理的理解
ThreadLoal 變數,它的基本原理是,同一個 ThreadLocal 所包含的物件(對ThreadLocal< String >而言即為 String 型別變數),在不同的 Thread 中有不同的副本(實際是不同的例項,後文會詳細闡述)。這裡有幾點需要注意

因為每個 Thread 內有自己的例項副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來
既然每個 Thread 有自己的例項副本,且其它 Thread 不可訪問,那就不存在多執行緒間共享的問題
既無共享,何來同步問題,又何來解決同步問題一說?
那 ThreadLocal 到底解決了什麼問題,又適用於什麼樣的場景?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

核心意思是

ThreadLocal 提供了執行緒本地的例項。它與普通變數的區別在於,每個使用該變數的執行緒都會初始化一個完全獨立的例項副本。ThreadLocal 變數通常被private static修飾。當一個執行緒結束時,它所使用的所有 ThreadLocal 相對的例項副本都可被回收。

總的來說,ThreadLocal 適用於每個執行緒需要自己獨立的例項且該例項需要在多個方法中被使用,也即變數線上程間隔離而在方法或類間共享的場景。後文會通過例項詳細闡述該觀點。另外,該場景下,並非必須使用 ThreadLocal ,其它方式完全可以實現同樣的效果,只是 ThreadLocal 使得實現更簡潔。

ThreadLocal用法

 java程式碼:

package Threads;

import java.util.concurrent.CountDownLatch;

/**
 * Created by xfyou 2018/5/25 18:32.
 */
public class ThreadLocalDemo {

    // 閉鎖需要等待的執行緒數量
    private static final int THREADS_COUNT = 3;

    public static void main(String[] args) throws InterruptedException {

        /*在實時系統中的使用場景
         *
         * 實現最大的並行性:有時我們想同時啟動多個執行緒,實現最大程度的並行性。
         * 例如,我們想測試一個單例類。如果我們建立一個初始計數為1的CountDownLatch,並讓所有執行緒都在這個鎖上等待,那麼我們可以很輕鬆地完成測試。我們只需呼叫一次countDown()方法就可以讓所有的等待執行緒同時恢復執行。
         * 開始執行前等待N個執行緒完成各自任務:例如應用程式啟動類要確保在處理使用者請求前,所有N個外部系統已經啟動和執行了。
         * 死鎖檢測:一個非常方便的使用場景是,你可以使用N個執行緒訪問共享資源,在每次測試階段的執行緒數目是不同的,並嘗試產生死鎖。
         */
        CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);

        InnerClass innerClass = new InnerClass();
        for (int i = 1; i <= THREADS_COUNT; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 4; j++) {
                        innerClass.add(String.valueOf(j));
                        innerClass.print();
                    }
                    innerClass.set("hello world");

                    /*
                     * 通知CountDownLatch物件,他們已經完成了各自的任務
                     * 每當一個執行緒完成了自己的任務後,計數器的值就會減1
                     * 所以當N個執行緒都呼叫了這個方法,count的值等於0,然後主執行緒就能通過await()方法,恢復執行自己的任務。
                     */
                    countDownLatch.countDown();
                }
            }, "Thread-" + i).start();
        }

        /*
         * 主執行緒必須在啟動其他執行緒後立即呼叫CountDownLatch.await()方法。這樣主執行緒的操作就會在這個方法上阻塞,直到其他執行緒完成各自的任務。
         * CountDownLatch是通過一個計數器來實現的,計數器的初始值為執行緒的數量。
         * 每當一個執行緒完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的執行緒已經完成了任務,然後在閉鎖(Latch)上等待的執行緒就可以恢復執行任務。
         */
        countDownLatch.await();

        System.out.println("所有執行緒執行完畢");
        System.out.println("主執行緒繼續執行。。。");
    }

    private static class InnerClass {
        void add(String newStr) {
            StringBuilder str = Counter.counter.get();
            Counter.counter.set(str.append(newStr));
        }

        void print() {
            System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString());
        }

        void set(String words) {
            Counter.counter.set(new StringBuilder(words));
            System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s,  Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString());
        }
    }

    private static class Counter {
        /*
         * get時如果執行緒本地變數為null,則預設初始化一個這個變數型別的例項。
         * StringBuilder為非執行緒安全的型別,通過ThreadLocal本地化則可以實現執行緒安全
         */
        private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() {
            @Override
            protected StringBuilder initialValue() {
                return new StringBuilder();
            }
        };
    }
}

 一種可能的執行結果如下:

Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:0
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:01
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:012
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:0123
Set, Thread name:Thread-2 , ThreadLocal hashcode:946838393,  Instance hashcode:997620193, Value:hello world

Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:0
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:01
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:012
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:0123
Set, Thread name:Thread-3 , ThreadLocal hashcode:946838393,  Instance hashcode:595251207, Value:hello world

Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:0
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:01
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:012
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:0123
Set, Thread name:Thread-1 , ThreadLocal hashcode:946838393,  Instance hashcode:787957613, Value:hello world

所有執行緒執行完畢
主執行緒繼續執行。。。

 執行結果分析:

1、所有執行緒訪問的都是同一個ThreadLocal變數,其hashcode為:946838393(各執行緒訪問的ThreadLocal在堆記憶體中的地址均為同一個);

2、各執行緒通過ThreadLocal物件的get()方法拿到的StringBuilder物件例項是不同的(hashcode不一樣,例項在堆記憶體中的地址不一樣);

3、各個執行緒將字串追加進各自的 StringBuidler 例項內;

4、使用 set(T t) 方法後,ThreadLocal 變數所指向的 StringBuilder 例項被替換。

相關文章