ThreadLocal使用詳解

ThreadLocal簡介

ThreadLocal主要解決多線程併發訪問致使數據不一致問題。 ThreadLocal爲每個使用該變量的線程都提供一個變量值的副本,雖然這種方式耗費內存,可是大大減小了線程同步所帶來性能消耗,也減小了線程併發控制的複雜度。java

舉個例子:將ThreadLocal比喻成存放數據的盒子,盒子中只能夠存儲盒子主人的私有數據。因爲數據空間是隔離的,所以,多線程併發訪問而互不影響,從而避免了線程安全的問題。安全

ThreadLocal 是如何作到爲每個線程維護變量的副本?其實實現思路很簡單,在ThreadLocal類中有一個ThreadLocalMap,用於存儲每個線程的變量的副本。bash

###ThreadLocal和Synchonized 的區別多線程

  1. ThreadLocal:使用副本機制,使每一個線程在某一時該訪問到的對象是不一樣的,從而保證線程間的數據隔離
  2. synchronized:使用鎖機制,使對象在某一時只能被一個線程訪問,從而保證線程間的數據共享

代碼示例

import java.util.*;

public class ThreadLocalDemo implements Runnable {

    // list 不是線程安全的,因此每一個線程都要有本身獨立的副本
    private static ThreadLocal<List> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo obj = new ThreadLocalDemo();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(obj, "thread" + i);
            Thread.sleep(1000);
            t.start();
        }
    }

    @Override
    public void run() {
        List<String> list = new ArrayList<>();
        list.add(Thread.currentThread().getName());
        threadLocal.set(list);
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        list.add("1");
        list.add("2");
        System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
    }

}
複製代碼

ThreadLocal源碼

  1. ThreadLocal 類的get()方法
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

其中ThreadLocalMap能夠理解爲定製化的HashMap,而ThreadLocal只是對其進行了封裝,並傳遞變量值,經過ThreadLocalMapget()方法獲取當前線程局部變量的值。併發

  1. ThreadLocal類的set()方法
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
複製代碼

實現原理和get()方法同樣,內部由ThreadLocalMap管理數據,set 時先檢查 map 是否爲空,若是不爲空就直接保存數據,若是爲空則先建立 map 再保存數據。ide

ThreadLocal內存泄露問題

ThreadLocalMap中使用的 key 爲弱引用,而 value 是強引用。因此,若是ThreadLocal沒有被外部強引用的狀況下,在GC 執行的時候會把 key 清理掉,而 value 不會被清理。這樣一來,ThreadLocalMap中就會出現 key 爲 null 的 Entry。假如不作任何措施的話,value 可能永遠沒法被 GC 回收,這個時候可能會產生內存泄漏。性能

以上狀況,ThreadLocalMap的實現已經考慮到了,在調用 set()get()remove()方法時,會清理 key 爲 null 的記錄。(咱們在使用完 ThreadLocal 後,最好手動調用 remove() 方法)ui

弱引用

若是一個對象只具備弱引用,那就屬於無關緊要的對象。弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。垃圾回收器線程執行時會掃描它所管轄的內存區域,一旦發現只具備弱引用的對象,無論當前內存空間足夠與否,都會將它回收。不過,因爲垃圾回收期是一個優先級很低的線程,所以不必定很快就會發現那些只具備弱引用的對象。this

弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用對象被垃圾回收後,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。spa

相關文章
相關標籤/搜索