併發編程之ThreadLocal

併發編程之ThreadLocal

首先說明一下啊,ThreadLocal其實不應該放在併發編程系列,只是在不知道放到哪合適

介紹

ThreadLocal翻譯爲"本地線程",但是這個類的取名似乎詞不達意了。也許使用ThradLocalVariable(線程本地變量)可能更合適。

JDK源碼描述:

ThreadLocal類用來提供線程內部的局部變量。

這種變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其他線程內的變量。

ThreadLocal實例通常來說都是private static類型的,用於關聯線程和線程的上下文。

1、每個線程都有自己的局部變量(該變量對其它線程不可見)

每個線程都有一個獨立於其他線程的上下文來保存這個變量,一個線程的本地變量對其他線程是不可見的

2、擁有獨立於變量的初始化副本

ThreadLocal可以給一個初始值,而每個線程都會獲得這個初始化值的一個副本,這樣才能保證不同的線程都有一份拷貝

3、狀態與某一線程相關聯

重點:ThreadLocal不是用來解決共享變量問題的,不是爲了協調線程同步而存在,而是爲了方便每個線程處理自己的狀態而引入的一種機制。

ThreadLocal案列解析理解

IntegerThread Code

/**
 * 
 * @類名: IntegerThread
 * @Time 2016年8月18日 下午2:01:12
 * @功能描述:基本數據類型的ThreadLocal
 * @author xuyi3
 * @春風十里不如你
 *
 */
public class IntegerThread implements Runnable
{

    // 基本數據類型的ThreadLocal使用
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
    {
        protected Integer initialValue()
        {
            return 0;
        }

    };

    public void run()
    {
        // 修改threadLocal的值
        int num = threadLocal.get();
        for (int j = 0; j < 10; j++)
        {
            num++;
            threadLocal.set(num);
        }
        System.out.println(Thread.currentThread().getName() + "---" + num);
    }

}

IndexThread Code

/**
 * 
 * @類名: IndexThread
 * @Time 2016年8月18日 下午2:02:06
 * @功能描述: 引用數據類型的ThreadLocal
 * @author xuyi3
 * @春風十里不如你
 *
 */
public class IndexThread implements Runnable
{

    // 定義ThreadLocal<Index> 變量
    private static ThreadLocal<Index> threadLocal = new ThreadLocal<Index>()
    {
        Index index = new Index();

        protected Index initialValue()
        {
            return index;
            // return new Index();
            // return index 和return new Index()結果是完全不同的。

            // 備註:使用ThreadLocal修飾引用數據類型時要特別注意。
        }
    };

    public void run()
    {
        for (int i = 0; i < 1000; i++)
        {
            threadLocal.get().incr();
        }
        System.out.println(Thread.currentThread().getName() + "---" + threadLocal.get().getNum());
    }
}

Main Code

public class Main
{
    public static void main(String[] args)
    {
        // ThreadLocal<Integer>案列展示
        IntegerThread integerThread = new IntegerThread();
        new Thread(integerThread).start();
        new Thread(integerThread).start();
        new Thread(integerThread).start();
        new Thread(integerThread).start();
        new Thread(integerThread).start();

        // ThreadLocal<Index>案列展示
        IndexThread indexThread = new IndexThread();

        new Thread(indexThread).start();
        new Thread(indexThread).start();
        new Thread(indexThread).start();
        new Thread(indexThread).start();
        new Thread(indexThread).start();
    }
}

分析IndexThread中initialValue方法,如果初始值返回的是index而不是new Index()會出現的同步問題。

t1時刻啓動線程1,
t2時刻線程1其將index的num值增加爲3,此時啓動線程2,線程2取出的index副本,就是改變之後的index(num爲3),
和線程1開始取出的副本index的num值已經不同了。


其根本原因在於每個線程持有副本的引用指向的內存地址都是相同的,詳細瞭解可看內存圖。

返回index引用的內存分析

返回index引用的內存分析


返回new Index()時內存分析

返回new Index()時內存分析

ThreadLocal源碼分析

/**
*ThreadLocal類用來提供線程內部的局部變量。
這種變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其他線程內的變量。 
ThreadLocal實例通常來說都是private static類型的,用於關聯線程和線程的上下文。
*/
public class ThreadLocal<T> {

    /**
     * 
     * 返回此線程局部變量的當前線程的“初始值”。
     *
     * 該實現返回 null;如果程序員希望線程局部變量具有 null 以外的值,則必須爲 ThreadLocal 
     * 創建子類,並重寫此方法。通常將使用匿名內部類完成此操作。
     *
     */
    protected T initialValue() {
        return null;
    }

    /**
     * 返回此線程局部變量的當前線程副本中的值。如果變量沒有用於當前線程的值,則先將其初始化爲調用 
     * initialValue() 方法返回的值。     * 
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

    /**
     * 將此線程局部變量的當前線程副本中的值設置爲指定值。大部分子類不需要重寫此方法,它們只依靠
     * initialValue() 方法來設置線程局部變量的值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


    //移除此線程局部變量當前線程的值
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //ThreadLocalMap值得單獨分析
    static class ThreadLocalMap {

            // ... 

    }
}

總結

ThreadLocal的作用是提供線程內的局部變量。

這種變量在線程的生命週期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度。

其實注意看框架源碼注意的話,就會發現其實蠻多地方是使用了ThreadLocal的,通常使用private static 修飾。

ThreadLocal千萬不要誤用,誤將其視爲解決併發同步的手段。

參考

1、https://www.ibm.com/developerworks/cn/java/j-5things15/
2、https://www.zhihu.com/question/23089780
3、http://my.oschina.net/clopopo/blog/149368

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