多執行緒之執行緒區域性變數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物件例項:
執行結果也是OK的。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(); } }
這裡要注意的是:需要自己為每個執行緒分配不同的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號,考勤彙報,業務結算,計算工資 排程的實現方式: