ThreadLocal基本使用以及源碼分析

當多線程訪問共享可變數據時,涉及到線程間同步的問題,並不是所有時候,都要用到共享數據,所以就需要線程封閉出場了。本文中的ThreadLocal就起到了線程封閉的作用。它提供了線程內的局部變量,不同線程之間不會相互干擾,這種變量在線程的生命週期內起作用,減少了同一個線程內多個函數或組件之間一些公共變量傳遞的複雜度。

通俗的說ThreadLocal具備三個特性:

  • 線程併發: 在多線程併發的場景下使用
  • 傳遞數據: 我們通過ThreadLocal在同一線程,不同組件中傳遞公共變量
  • 線程隔離: 每個線程的變量都是獨立的,不會相互影響

總而言之,ThreadLocal的核心作用就是將變量在線程中隔離。

ThreadLocal的基本使用

常用方法

我們先看一下它的類圖中的方法:
在這裏插入圖片描述

方法申明 描述
ThreadLocal() 創建ThreadLocal對象
public void set(T value) 設置當前線程綁定的局部變量
public T get() 獲取當前線程綁定的局部變量
public void remove() 移除當前線程綁定的局部變量

例子

線程之間的變量非獨立

/**
 * Description
 * 線程隔離例子
 * 在多線程併發場景下,每個線程中的變量都是相互獨立的
 * 線程A:設置(變量1)      獲取(變量1)
 * 線程B:設置(變量2)      獲取(變量2)
 * Date 2020/6/3 22:03
 * Created by kwz
 */
public class ThreadLocalExample1 {

    @Getter
    @Setter
    private String content;

    public static void main(String[] args) {
        ThreadLocalExample1 example = new ThreadLocalExample1();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(
                    () -> {
                        //每個線程存一個變量,過一會兒取這個變量
                        example.setContent(Thread.currentThread().getName() + "的數據");
                        System.out.println("----------------------------------------");
                        System.out.println(Thread.currentThread().getName() + "---->" + example.getContent());
                    }
            );
            //0->4一共有5個線程
            thread.setName("線程" + i);
            thread.start();
        }
    }
}

我們看它的控制檯輸出結果:

----------------------------------------
----------------------------------------
----------------------------------------
線程2---->線程2的數據
線程0---->線程2的數據
----------------------------------------
線程4---->線程4的數據
----------------------------------------
線程1---->線程2的數據
線程3---->線程4的數據

我們可以看到一個線程取到了其他線程的變量

線程之間的變量相互獨立

/**
 * Description(利用ThreadLocal)
 * 線程隔離例子
 * 在多線程併發場景下,每個線程中的變量都是相互獨立的
 * 線程A:設置(變量1)      獲取(變量1)
 * 線程B:設置(變量2)      獲取(變量2)
 *
 * ThreadLocal
 * 1.set(): 將變量綁定到
 * Date 2020/6/3 22:03
 * Created by kwz
 */
public class ThreadLocalExample2 {

    ThreadLocal<String> t1 = new ThreadLocal<>();

    private String content;

    private String getContent(){
        String s = t1.get();
        return s;
    }

    private void setContent(String content){
        //變量content綁定到當前線程
        t1.set(content);
    }

    public static void main(String[] args) {
        ThreadLocalExample2 example = new ThreadLocalExample2();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(
                    () -> {
                        //每個線程存一個變量,過一會兒取這個變量
                        example.setContent(Thread.currentThread().getName() + "的數據");
                        System.out.println("----------------------------------------");
                        System.out.println(Thread.currentThread().getName() + "---->" + example.getContent());
                    }
            );
            //0->4一共有5個線程
            thread.setName("線程" + i);
            thread.start();
        }
    }
}

我們看它的控制檯輸出結果:

----------------------------------------
----------------------------------------
----------------------------------------
線程4---->線程4的數據
----------------------------------------
線程1---->線程1的數據
線程2---->線程2的數據
----------------------------------------
線程3---->線程3的數據
線程0---->線程0的數據

通過兩個例子可以看到,通過引入ThreadLocal可以做到不同線程之前訪問變量的相互獨立性。

ThreadLocal和Synchronized關鍵字

Synchronized的同步方式

線程之間共享變量的相互隔離,我們首先想到的其實是Synchronized,我們通過下面的Synchronized也能夠實現

public class ThreadLocalExample3 {

    @Getter
    @Setter
    private String content;

    public static void main(String[] args) {
        ThreadLocalExample3 example = new ThreadLocalExample3();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(
                    () -> {
                        //每個線程存一個變量,過一會兒取這個變量
                        synchronized (ThreadLocalExample3.class) {
                            example.setContent(Thread.currentThread().getName() + "的數據");
                            System.out.println("----------------------------------------");
                            System.out.println(Thread.currentThread().getName() + "---->" + example.getContent());
                        }
                    }
            );
            //0->4一共有5個線程
            thread.setName("線程" + i);
            thread.start();
        }
    }
}

上面的這段代碼也能起到線程對於共享變量的隔離效果,但是需要線程挨個排隊去實現業務,這樣就失去了多線程併發執行的意義了。

ThreadLocal和Synchronized的區別

雖然ThreadLocal模式和Synchronized關鍵字都用於多線程併發訪問變量的問題,不過兩者處理問題的角度和思路的不同的

Synchronized ThreadLocal
原理 同步機制採用了以時間換空間的方式,只提供了一份變量,讓不同線程排隊訪問 ThreadLocal採用了以空間換時間的方式,爲每個線程都提供了一份變量的副本,從而實現同時訪問而互不干擾
側重點 多個線程之間訪問資源的同步 多個線程中讓每個線程之間的數據相互隔離

ThreadLocal和Synchronized都能解決問題,但是使用ThreadLocal更爲合適,因爲這樣可以讓程序擁有更高的併發性

ThreadLocal的運用場景

  • 如在銀行證券相互轉賬時,我們手動開啓事務,直接獲取當前線程綁定的連接對象,如果連接對象是空的再去連接池中獲取連接,將此連接對象跟當前線程進行綁定。

使用ThreadLocal的優勢

  • 傳遞數據: 保存每個線程綁定的數據,在需要的地方可以直接獲取,避免參數直接傳遞帶來的代碼耦合性問題
  • 線程隔離: 各線程之間的數據相互隔離卻又具有併發性,避免同步方式帶來的性能損失

ThreadLocal的設計及源碼分析

ThreadLocal的設計

JDK1.8中ThreadLocal的設計原則是:每個Thread維護一個ThreadLocalMap,這個Map的key是ThreadLocal實例本身,value纔是真正要存儲的值object。

過程如下:

  • 每個Thread線程內部都有一個Map(ThreadLocalMap)
  • Map裏面存儲ThreadLocal對象(key) 和線程的變量副本(value)
  • Thread內部的Map是由ThreadLocal維護的,由ThreadLocal負責向map獲取和設置線程的變量值
  • 對於不同的線程,每次獲取副本值時,別的線程並不能獲取到當前線程的副本值,形成副本的隔離,互不干擾

ThreadLocal的源碼分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章