Java并發編程:線程封閉和ThreadLocal詳解

轉載請標明出處:
http://blog.csdn.net/forezp/article/details/77620769
本文出自方志朋的博客

什么是線程封閉


當訪問共享變量時,往往需要加鎖來保證數據同步。一種避免使用同步的方式就是不共享數據。如果僅在單線程中訪問數據,就不需要同步了。這種技術稱為線程封閉。在Java語言中,提供了一些類庫和機制來維護線程的封閉性,例如局部變量和ThreadLocal類,本文主要深入講解如何使用ThreadLocal類來保證線程封閉。

理解ThreadLocal類

ThreadLocal類能使線程中的某個值與保存值的對象關聯起來,它提供了get、set方法,這些方法為每個使用該變量的線程保存一份獨立的副本,因此get總是set當前線程的set最新值。

首先我們來看個例子,這個例子來自于http://www.cnblogs.com/dolphin0520/p/3920407.html


public class Test1 {

    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();


    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }
    public static void main(String[] args) throws InterruptedException {
        final Test1 test = new Test1();


        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread(() -> {
            test.set();
            System.out.println(test.getLong());
            System.out.println(test.getString());
        });
        thread1.start();
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

運行該程序,代碼輸出的結果為:

1

main

10

Thread-0

1

main

從這段代碼可以看出在mian線程和thread1線程確實都保存著各自的副本,它們的副本各自不干擾。

ThreadLocal源碼解析

來從源碼的角度來解析ThreadLocal這個類,這個類存放在java.lang包,這個類有很多方法。

image.png

它內部又個ThreadLocalMap類,主要有set()、get()、setInitialValue 等方法。

首先來看下set方法,獲取當前Thread的 map,如果不存在則新建一個并設置值,如果存在設置值,源碼如下:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

跟蹤createMap,可以發現它根據Thread創建來一個ThreadLocalMap。

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

t.threadLocals為當前線程的一個變量,也就是ThreadLocal的數據都是存放在當前線程的threadLocals變量里面的,由此可見用ThreadLocal存放的數據是線程安全的。因為它對于不同的線程來,使用ThreadLocal的set方法都會根據線程判斷該線程是否存在它的threadLocals成員變量,如果沒有就建一個,有的話就存下數據。

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap為ThreadLocal的一個內部類,源碼如下:

 static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

在使用ThreadLocal的get方法之前一定要先set,要不然會報空指針異常。還有一種方式就是在初始化的時候調用initialValue()方法賦值。改造下之前的例子,代碼如下:

public class Test2 {

    ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

        @Override
        protected Long initialValue() {
            return Thread.currentThread().getId();
        }
    };
    ThreadLocal<String> stringLocal = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final Test2 test = new Test2();



        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread(() -> {
          
            System.out.println(test.getLong());
            System.out.println(test.getString());
        });
        thread1.start();
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

運行該程序,代碼輸出的結果為:

1

main

10

Thread-0

1

main

ThreadLocal常用的使用場景

通常講JDBC連接保存在ThreadLocal對象中,每個對象都有屬于自己的連接,代碼如下:

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
    public Connection initialValue() {
       return DriverManager.getConnection(DB_URL);
    }
};
 
public static Connection getConnection() {
    return connectionHolder.get();
}

參考資料

《Java并發編程實戰》

《深入理解JVM》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 ThreadLocal很多同學都搞不懂是什么東西,可以用來干嘛。但面試時卻又經常問到,所以這次我和大家一起學...
    liangzzz閱讀 12,485評論 14 228
  • Android Handler機制系列文章整體內容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 7,665評論 4 30
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,341評論 11 349
  • 原創文章&經驗總結&從校招到A廠一路陽光一路滄桑 詳情請戳www.codercc.com 1. ThreadLoc...
    你聽___閱讀 6,758評論 8 19
  • 今天看到一首歌名,叫《三生三世》,“前世我是你桌上的蠟燭,點點燭光照亮你的臉龐。今生我求佛化作一株曇花,含苞待放在...
    足下阿蒙閱讀 276評論 0 1