(8)線程自留地——ThreadLocal

    在併發編程中,爲了線性安全我們經常要使用各種各樣的“鎖”。 不管鎖的粒度如何小,哪怕是CAS操作,都存在先來後到的排隊問題。有時候我們只是單純地計數,沒有太複雜的操作,想要一種不涉及鎖的線程安全的操作——ThreadLocal就是一種無鎖的線程安全的設計。

    當使用ThreadLocal修飾變量時,ThreadLocal爲每個線程(thread)設置了一個獨立的局部(local)變量副本,每個線程的變量副本互不干擾,從而實現了線程安全性。

一、ThreadLocal使用

    我們創建一個ThreadLocal變量count,用兩個不同的線程使用同一個變量,並且一個快一個慢(sleep100ms 和sleep 200ms)看它們能否各自獨立計數。

public class MainTest {
    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {  //重寫了initialValue 爲了給count重初值
            return new Integer(0);
        }
    };

    public static int getNext(){
        int data = count.get() + 1;
        count.set(data);
        return data;
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println(Thread.currentThread().getName() + " " + getNext());
                        Thread.sleep(100);  //這個線程每次sleep 100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        System.out.println(Thread.currentThread().getName() + " " + getNext());
                        Thread.sleep(200);   //這個線程每次sleep 200毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

    運行結果

  可以看到兩個線程使用了同一個ThreadLocal變量,各自獨立計數一個快一個慢,且沒有出現線程安全性問題。

ThreadLocal使用時要先用set()再用get(), 因爲默認的initialValue()裏value的值設置爲null,如果不先set()就要重寫initialValue()方法,否則會拋nullPointerException。

二、原理與源碼解析

    ThreadLocal<T> 類有一個靜態內部類ThreadLocalMap,ThreadLocalMap裏有一個Entry內部類組成的數組。

    ThreadLocal通過get()與set()方法來獲取和存儲變量數據到數組中。

    set()方法

  

  當一個線程中調用了ThreadLocal的set方法時,首先獲取本線程的threadID,然後獲取本線程內部的ThreadLocalMap,用ThreadLocal值和value創始一個Entry對象存入自身所在的ThreadLocalMap中。因此,各個線程的ThreadLocal和value值都存儲在各線程內部,互不干擾。

    get()方法

  

  get方法與set方法類似。當一個線程調用了ThreadLocal的get方法時,獲取本線程的threadID和本線程內部的ThreadLocalMap,然後取出Entry對象獲存儲的值。也是各線程互不干擾。

 

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