1. 程式人生 > >多執行緒之執行緒區域性變數ThreadLocal及原理

多執行緒之執行緒區域性變數ThreadLocal及原理

一、執行緒區域性變數ThreadLocal

ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數。既然是隻有當前執行緒可以訪問的資料,自然是執行緒安全的。

主要方法:


initialValue()方法可以重寫,它預設是返回null。

下面來看一個例子:

public class ThreadLocalTest {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static class ParseDate implements Runnable{
        int i = 0;
        public ParseDate(int i){this.i = i;}
        @Override
        public void run() {
            try{
                Date t = sdf.parse("2017-07-16 10:34:" + i % 60);
                System.out.println(i + ":" + t);
            }catch (ParseException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i = 0;i<10;i++){
            es.execute(new ParseDate(i));
        }
        es.shutdown();
    }
}
執行上面的程式可以回得到下面的異常:

因為SimpleDateFormat.parse方法並不是執行緒安全的,因此線上程池中共享這個物件必然導致錯誤。

一種可行的方法就是加鎖:

public class ThreadLocalTest {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static class ParseDate implements Runnable{
        int i = 0;
        public ParseDate(int i){this.i = i;}
        private static ReentrantLock lock = new ReentrantLock();
        @Override
        public void run() {
            try{
                lock.lock();
                Date t = sdf.parse("2017-07-16 10:34:" + i % 60);
                System.out.println(i + ":" + t);
                lock.unlock();
            }catch (ParseException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i = 0;i<10;i++){
            es.execute(new ParseDate(i));
        }
        es.shutdown();
    }
}
執行結果:

我們也可以用ThreadLocal為每一個執行緒都產生一個SimpleDateFormat物件例項:

public class ThreadLocalTest {
    static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<>();
    public static class ParseDate implements Runnable{
        int i = 0;
        public ParseDate(int i){this.i = i;}
        @Override
        public void run() {
            try{
                if(tl.get() == null){
                    tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                }
                Date t = tl.get().parse("2017-07-16 10:34:" + i % 60);
                System.out.println(i + ":" + t);
            }catch (ParseException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for(int i = 0;i<10;i++){
            es.execute(new ParseDate(i));
        }
        es.shutdown();
    }
}
執行結果也是OK的。
這裡要注意的是:需要自己為每個執行緒分配不同的SimpleDateFormat物件,ThreadLocal只是起到了簡單的容器的作用。如果在應用上為每一個執行緒分配了相同的物件例項,那麼ThreadLocal也不能保證執行緒安全。

看到這裡可能你會問:上面這個例子,把ThreadLocal換成,直接在ParseDate中建立一個成員變數Private SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:dd”)不也可以達到同樣的效果。當然這樣效果跟ThreadLocal是一樣的,ThreadLocal只是提供了一個容器,容納這些需要在每個執行緒上都互不干擾的變數的副本。

二、ThreadLocal原始碼分析

下面我們來分析下ThreadLocal的原始碼,看看是怎麼保證這些物件只被當前執行緒所訪問。

首先我們要先了解ThreadLocal中的一個靜態內部類:ThreadLocalMap
ThreadLocalMap是一個類似HashMap的東西,更準確的說是WeakHashMap。
進一步檢視ThreadLocalMap的實現,可以看到它由一系列的Entry構成:

static class Entry extends WeakReference<ThreadLocal> {
            Object value;


            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
可以看到ThreadLocal的實現使用了弱引用。為什麼要使用弱引用呢?
先看看WeakRefercence的特點:
WeakReference是Java語言規範中為了區別直接的物件引用(程式中通過建構函式宣告出來的物件引用)而定義的另外一種引用關係。WeakReference標誌性的特點是:reference例項不會影響到被應用物件的GC回收行為(即只要物件被除WeakReference物件之外所有的物件解除引用後,該物件便可以被GC回收),只不過在被物件回收之後,reference例項想獲得被應用的物件時程式會返回null。


ThreadLocalMap中的每個Entry都引用了ThreadLocal例項,如果ThreadLocal例項是強引用,那麼即使把ThreadLocal的例項設為null,但這個例項在ThreadLocalMap中還有引用,導致無法被GC回收。宣告為WeakReference的話,ThreadLocal例項在ThreadLocalMap中的引用就為弱引用,那麼把ThreadLocal例項設為null後,它就可以被GC回收了。當然,如果使用完ThreadLocal例項的話,最好是用threadLocal.remove()來代替threadLocal = null。


主要看ThreadLocal的set()和get()方法。

首先我們要知道每個Thread例項都有一個ThreadLocalMap型別的成員變數:

ThreadLocal.ThreadLocalMap threadLocals = null;

set()方法:
public void set(T value) {
        Thread t = Thread.currentThread();   //拿到當前執行緒
        ThreadLocalMap map = getMap(t);   //拿到當前執行緒t的那個ThreadLocalMap型別的成員變數
        if (map != null)
            map.set(this, value); //map不為null,就把鍵為該threadLocal的entry的值設定為value        
        else
            createMap(t, value);//map為null,就為當前執行緒的那個成員變數new一個ThreadLocalMap並加入一個鍵為該threadLocal,值為value的Entry。
}
get()方法:
public T get() {
        Thread t = Thread.currentThread();  //拿到當前執行緒
        ThreadLocalMap map = getMap(t);  //拿到當前執行緒的那個ThreadLocalMap型別的成員變數
        if (map != null) {     
            ThreadLocalMap.Entry e = map.getEntry(this);//map不為null,取出鍵為該threadLocal的Entry物件
            if (e != null)
                return (T)e.value; //存在這個Entry物件,就返回它的值
        }
        return setInitialValue();  //map為null,就為當前執行緒的那個成員變數new一個ThreadLocalMap並加入一個鍵為該threadLocal,值為初始值的Entry
    }


總結一下:

1、變數的副本是通過ThreadLocalMap來儲存,鍵為ThreadLocal例項(每個執行緒可以有多個ThreadLocal例項),值為變數的值。

2、每個執行緒都有一個ThreadLocalMap型別的threadLocals 變數,實際也就儲存在這。

3、一般要在get()之前先set(),否則會丟擲空指標異常,除非重寫initialValue方法。

相關推薦

執行執行區域性變數ThreadLocal原理

一、執行緒區域性變數ThreadLocal ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數。既然是隻有當前執行緒可以訪問的資料,自然是執行緒安全的。 主要方法: initialValue()方法可以重寫,它預設是返回

jmeter執行併發時,區域性變數和全域性變數的區別

1. 業務場景5個使用者登入系統,需要將登入介面A返回的token作為介面B的入參。介面B設定集合點,同時請求後臺介面。2. 指令碼2.1  目錄結構                2.2  多個使用者資訊獲取                採用csv檔案儲存使用者資訊   

Java執行執行範圍內共享變數的概念與作用

要實現執行緒範圍內的資料共享,就是說不管是A模組還是B模組,如果它們現在在同一個執行緒上執行,它們操作的資料應該是同一個,下面的做法就不行: package javaplay.thread.test; import java.util.Random; public

執行 執行的生命週期

執行緒是存在生命週期的。執行緒的生命週期分為五種狀態: 1.新建狀態 當執行緒使用new 關鍵字建立了一個執行緒以後,執行緒就出於新建狀態。和其他物件一樣,僅僅被分配記憶體,並初始化成員變數的值。 2.準備狀態 當執行緒呼叫start()方法後進入準備狀態。由cpu來決定哪個執行緒進入

執行 執行簡介

什麼是程序? 所有執行中的程式通常對應一個程序。當一個程式進入記憶體執行時,就會變成一個程序。 程序是出於執行過程中的程式,並且具有一定的獨立功能。 程序是系統進行資源分配和排程的獨立單位。 程序包含散打特徵: 1.獨立性。程序是系統中獨立存在的實體,擁有自己獨立的資源,每一個程序都

java執行 執行協作

也是網上看的一道題目:關於假如有Thread1、Thread2、Thread3、Thread4四條執行緒分別統計C、D、E、F四個盤的大小,所有執行緒都統計完畢交給Thread5執行緒去做彙總,應當如何實現? 蒐集整理了網上朋友提供的方法,主要有: 1. 多執行緒都是Thread或

從零開始學執行執行池(五)

單執行緒的缺點&使用多執行緒的好處 圍繞執行任務來管理應用程式時,第一步要指明一個清晰的任務邊界(task boundaries).理想情況下,任務是獨立的活動:它的工作並不依賴於其他任務的狀態、結果或者邊界效應.獨立有利於併發性,如果能得到相應的處理器資源,獨立的任務還可以並行執行.

執行執行安全關鍵字synchronized

    synchronized關鍵字,是多執行緒程式設計時保證執行緒安全使用非常廣泛的java知識。下面我們學習下synchronized的相關知識: 實現原理     synchronized的實現原理是基於記憶體中的lock原則。記憶體模型中的變

Java基礎執行執行安全-同步鎖三種形式

首先,我們通過一個案例,演示執行緒的安全問題: 電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共100個(本場電影只能賣100張票)。我們來模擬電影院的售票視窗,實現多個視窗同時賣 “終結者”這場電影票(多個視窗一起賣這100張票)需要視窗

Java基礎學習——執行執行

1.執行緒池介紹     執行緒池是一種執行緒使用模式。執行緒由於具有空閒(eg:等待返回值)和繁忙這種不同狀態,當數量過多時其建立、銷燬、排程等都會帶來開銷。執行緒池維護了多個執行緒,當分配可併發執行的任務時,它負責排程執行緒執行工作,執行完畢後執行緒不關閉而是返回執行緒池,

linux執行 執行資料TSD Thread Specific Data

在linux中,同一程序下所有執行緒是共享全域性變數的,但有時候會有這樣的需求,某一個執行緒想單獨用於某個全域性變數。這就有了TSD,Thread Specific Data。 使用TSD時需要建立一個全域性的鍵值,每個執行緒都通過鍵值來設定和獲取自己所獨有的全域性變數。 使用TSD分為

Java執行執行排程(二)

(一)執行緒優先順序 執行緒優先順序用1~10表示,10表示優先順序最高,預設值是5.每個優先順序對應一個Thread類的公用靜態常量。如 public static final int MIN_PRIORITY = 1; public static final int NO

java執行-執行間的通訊

一個生產者與一個消費者 使用的方法: wait():使執行緒停止並釋放鎖。 notify():叫醒執行緒。 例子 工具類 public class ValueObject { public static String value=""; }

Java筆記-執行執行控制

執行緒控制 我們已經知道了執行緒的排程,接下來我們就可以使用如下方法物件執行緒進行控制。 1.執行緒休眠 public static void sleep(long millis):讓當前執行緒處於暫停狀態,millis引數毫秒值,即暫停時間。 程式

Java筆記-執行執行死鎖問題加簡單舉例

死鎖 導致死鎖的原因 Java中死鎖最簡單的情況是,一個執行緒T1持有鎖L1並且申請獲得鎖L2,而另一個執行緒T2持有鎖L2並且申請獲得鎖L1,因為預設的鎖申請操作都是阻塞的,所以執行緒T1和T2永遠被阻塞了。導致了死鎖。 這是最容易理解也是最簡單的死

[JDK] Java 執行 執行安全

Java 多執行緒 之 執行緒安全 多執行緒併發操作時資料共享如何安全進行? 執行緒安全與共享 多執行緒操作靜態變數(非執行緒安全) SynchronizedLockTest: /** * <p> * 測試類 * </p>

讀書筆記:java執行執行同步

閱讀的書籍:《java瘋狂講義》 關鍵詞:執行緒安全問題,同步程式碼塊,同步方法,釋放同步監視器的鎖定,同步鎖,死鎖 執行緒安全問題:當使用多個執行緒來訪問同一個資料時,會導致一些錯誤情況的發生 到底什麼是執行緒安全問題呢,先看一個經典的案例:銀行取錢的問題

從零開始學執行執行安全(一)

public class Employees { 2 //程式設計師的等級 3 private int level; 4 //技能庫 5 public Map<String,String> skills; 6 7 //工資 8 pr

Java執行執行的狀態以及之間的切換(轉)

 博主最近幾天在面試的時候,被面試官問到了Java多執行緒的幾種狀態,無疑博主已經把該忘記的都忘記了,很是尷尬,回到家中在網上找到一篇部落格,博主認真閱讀了此文章,寫的很詳細,現轉載分享給大家: Java中執行緒的狀態分為6種。     1. 初始(N

Java執行執行排程詳解

排程的概念 給定時間結點,時間間隔,任務次數,然後自動執行任務 應用場景舉例 1.日誌清理:每隔三個月,清理公司日誌檔案 2.日誌備份:每個一週,備份公司檔案 3.工資結算:每個月29號,考勤彙報,業務結算,計算工資 排程的實現方式: