1. 程式人生 > >多執行緒(六):ThreadLocal 關鍵字

多執行緒(六):ThreadLocal 關鍵字

包括:

一. 什麼是 ThreadLocal

二. ThreadLocal 類中的方法簡介

三. 如何使用ThreadLocal

     3.1 3個執行緒共享一個物件,各自產生序列號

     3.2 例子:共享變數a, 使用ThreadLocal 和沒有使用的區別

四. ThreadLocal 和同步機制的比較

五.參考

一.什麼是ThreadLocal

       ThreadLocal並非一個執行緒,而是一個執行緒區域性變數。它的作用就是為使用該變數的執行緒都提供一個變數值的副本,每個執行緒都可以獨立的改變自己的副本,而不會和其他執行緒的副本造成衝突。

       從執行緒的角度看,每個執行緒都保持一個對其執行緒區域性變數副本的隱式引用,只要執行緒是活動的並且 ThreadLocal 例項是可訪問的;線上程消失之後,其執行緒區域性例項的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。

       通過ThreadLocal存取的資料,總是與當前執行緒相關,也就是說,JVM 為每個執行的執行緒,綁定了私有的本地例項存取空間,從而為多執行緒環境常出現的併發訪問問題提供了一種隔離機制。

       ThreadLocal是解決執行緒安全問題一個很好的思路,ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值對應執行緒的變數副本,由於Key值不可重複,每一個“執行緒物件”對應執行緒的“變數副本”,而到達了執行緒安全。

      概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。

二.ThreadLocal類中的方法簡介

2.1 get()方法:

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();

}

       返回此執行緒區域性變數的當前執行緒副本中的值。首先得到該執行緒t,由於ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數的副本,所以使用ThreadLocalMap map = getMap(t);方法取得當前執行緒副本中的值,如果這是執行緒第一次呼叫該方法,即map為空,那麼呼叫setInitialValue()方法建立並初始化此副本。

2.2 remove()方法

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

       移除此執行緒區域性變數當前執行緒的值。這可能有助於減少執行緒區域性變數的儲存需求。如果再次訪問此執行緒區域性變數,那麼在預設情況下它將擁有其 initialValue。

三.如何使用ThreadLocal
3.1 3個執行緒共享一個物件,各自產生序列號
public class SequenceNumber {
	
	//通過一個匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
	private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
		public Integer initialValue(){
			return 0;
		}
	};
	
	//獲取下一個序列號
	public int getNextNum(){
		seqNum.set(seqNum.get() + 1);
		return seqNum.get();
	}
	
	public static void main(String args[]){
		
		SequenceNumber sn = new SequenceNumber();
		
		ThreadClient t1 = new ThreadClient(sn);
		ThreadClient t2 = new ThreadClient(sn);
		ThreadClient t3 = new ThreadClient(sn);
		
		t1.start();
		t2.start();
		t3.start();
	}
	
	private static class ThreadClient extends Thread
	{
		private SequenceNumber sn;
		
		public ThreadClient(SequenceNumber sn)
		{
			this.sn = sn;
		}
		
		public void run(){
			for(int i = 0; i < 3; ++i)
			{
				System.out.println("Thread[" + Thread.currentThread().getName() +
						"] sn[" + sn.getNextNum() + "]");
			}
		}
	}
	
}

       那麼,對於這個例子來說,首先是3個執行緒共享了SequenceNumber物件,那麼如何才能保證用同一個東西,但是互不影響,這時候就用到了ThreadLocal來實現這個功能。ThreadLocal類中的Map,儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值對應執行緒的變數副本。所以,各個執行緒都是在各自的副本上做操作,互不影響。

概況說下上述程式碼流程:

  1. 首先在SequenceNumber類中建立ThreadLocal物件seqNum用來儲存執行緒間需要隔離處理的物件
  2. 建立一個獲取要隔離訪問的資料的方法getXxx(),比如上述的publicint getNextNum()方法,主要是裡面的seqNum.get()方法。
  3. 在run()方法中,通過getXxx()方法獲取要操作的資料,這樣可以保證每個執行緒對應一個數據物件,在任何時刻都操作的是這個物件。

       可能會有個疑問:建立3個執行緒,這3個執行緒是什麼時候存到ThreadLocal的Map裡面的,而且是怎麼存放的,get方法取出的時候怎麼知道取哪一個。

       首先解決是什麼時候這3個執行緒存放到Map裡面的,在上述程式碼呼叫publicint getNextNum()中的seqNum.get()方法,我們看一下這個get方法:

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();
    }
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;
    }
       首先呼叫get方法的時候,先取得當前的執行緒t,然後看一下這個鍵對值map有沒有,沒有的話,呼叫setInitialValue()返回一個初始的,並且加入到map中,有的話就返回這個值。所以說,這3個執行緒以及他們各個的副本的值什麼時候存放在Threadlocal的map中,就是在get方法呼叫的時候。

3.2  例子:共享變數a,使用ThreadLocal和沒有使用的區別

public class Test {
	public static int a = 0;
	
	public static void main(String args[])
	{
		MyThread myThread = new MyThread();
		myThread.start();
		
		for(int i = 0; i < 5; ++i)
		{
			a = a + 1;
			System.out.println(Thread.currentThread().getName() + ":" + a);
		}
	}
	
	public static class MyThread extends Thread
	{
		public void run()
		{
			for(int i = 0; i < 5; ++i)
			{
				a = a + 1;
				System.out.println(Thread.currentThread().getName() + ":" + a);
			}
		}
	}
}

輸出結果為:

main:2
main:3
main:4
main:5
main:6
Thread-0:2
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10


在沒有同步的機制下,該結果有不安全的情況發生是正常的,兩個執行緒都得到2。那麼,當使用了ThreadLocal的時候:
 public class Test {
	
	private static ThreadLocal<Integer> a = new ThreadLocal<Integer>(){
		public Integer initialValue(){
			return 0;
		}
	};
	
	public static void main(String args[])
	{
		MyThread myThread = new MyThread();
		myThread.start();
		
		for(int i = 0; i < 5; ++i)
		{
			a.set(a.get() + 1);
			System.out.println(Thread.currentThread().getName() + ":" + a.get());
		}
	}
	
	public static class MyThread extends Thread
	{
		public void run()
		{
			for(int i = 0; i < 5; ++i)
			{
				a.set(a.get() + 1);
				System.out.println(Thread.currentThread().getName() + ":" + a.get());
			}
		}
	}
}

輸出結果為:

main:1
main:2
main:3
main:4
main:5
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5

再次分析下如何使用使用TheadLocal:

1. 在多執行緒的類(如上述的Test類)中,建立一個ThreadLocal物件a.

2. 建立一個獲取要隔離訪問的資料的方法getXxx().(上述直接使用THreadlocal的get方法)。

3. 在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的資料這樣可以保證每個執行緒對應一個數據物件,在任何時刻都操作的是這個物件。(上述例子直接就使用THreadLocal的get方法)

四.ThreadLocal和同步機制的比較

       ThreadLocal和執行緒同步機制相比有什麼優勢呢?ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題

       在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。

        而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個線 程對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編 寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。

       當 然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了 同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的 有效方式而ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個線 程之間共享資源(變數),這樣當然不需要對多個執行緒進行同步了。所 以,如果你需要進行多個執行緒之間進行通訊,則使用同步機制;如果需要隔離多個執行緒之間 的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程 序,使程式更加易讀、簡潔。

       概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。

五.參考

大多數內容都是以下3個網址的內容,然後對其中的一些例子加上自己的理解,有意見或者建議歡迎指出。










相關推薦

執行ThreadLocal 關鍵字

包括: 一. 什麼是 ThreadLocal 二. ThreadLocal 類中的方法簡介 三. 如何使用ThreadLocal      3.1 3個執行緒共享一個物件,各自產生序列號     

執行2synchronized關鍵字

  多執行緒操作相同資源的時候,會出現執行緒安全的問題,導致結果與預期的不一致。   如下例子,設計四個執行緒,其中兩個對執行緒對變數加1操作,兩個執行緒對變數減1操作。理想狀態是,執行緒順序執行,相同次數的加減操作,最後變數的值不變。 1.執行緒不安全的操作 p

執行1繼承Thread類和實現Runnable介面

多執行緒的兩種實現方法: 1.繼承Thread類     繼承Thread類,重寫run()方法。建立多執行緒的時候,需要建立物件例項,然後呼叫start()方法。類物件的屬性屬於執行緒私有,執行緒之間互不影響。 public class ClassExtendT

深入理解java執行

關於java多執行緒的概念以及基本用法:java多執行緒基礎 6,單例模式與多執行緒 如何使單例模式遇到多執行緒是安全的這是下面要討論的內容 6.1,立即載入 立即載入就是在使用類的時候已經將物件建立完畢,例如String s = new Stri

Java執行執行基礎及建立

(一)、執行緒的生命週期 新建狀態: 使用 new 關鍵字和 Thread 類或其子類建立一個執行緒物件後,該執行緒物件就處於新建狀態。它保持這個狀態直到程式 start() 這個執行緒。 就緒狀態: 當執行緒物件呼叫了start()方法之後,該執行緒就進入就緒

Java 執行之Java記憶體模型

1. 併發程式設計的兩個問題 在併發程式設計中, 需要處理兩個關鍵問題: 執行緒之間如何通訊及執行緒之間如何同步 通訊指的是執行緒之間是以何種機制來交換資訊, 在指令式程式設計中, 執行緒之間的通訊機制有兩種:共享記憶體和訊息傳遞。在共享記憶體的模型中, 執行緒之間共享程式的公共狀態, 通過讀寫記憶體中的

Java執行之Deque與LinkedBlockingDeque深入分析

1、LinkedBlockingDeque資料結構 雙向併發阻塞佇列。所謂雙向是指可以從佇列的頭和尾同時操作,併發只是執行緒安全的實現,阻塞允許在入隊出隊不滿足條件時掛起執行緒,這裡說的佇列是指支援FIFO/FILO實現的連結串列。 首先看下LinkedBlockingDeque的資料結構。通常情況

程序與執行--LinuxThreads(轉)

(注:這篇文章轉自網路,雖然Linux從核心2.6開始,多執行緒已使用NPTL技術,但是這篇文章對我們理解多執行緒技術還是挺有用的)Linux核心對多程序和多執行緒的支援方式:        執行緒機制支援併發程式設計技術,在多處理器上能真正保證並行處理。而在linux實現執行緒很特別,linux把所有的執行

java執行1執行的建立和執行的安全問題

前言 java多執行緒多用於服務端的高併發程式設計,本文就java執行緒的建立和多執行緒安全問題進行討論。 正文 一,建立java執行緒 建立java執行緒有2種方式,一種是繼承自Thread類,另一種是實現Runnable介面。由於java只支援單

執行——暫停、恢復、停止執行較好的方式,sleep/wait/yield區別

不推薦的 大家都知道:suspend()、resume()、stop()這些方法已經被廢棄了。suspend()、resume()如果使用不當,容易造成公共的同步物件資源的獨佔以及導致資料不同步。用官網的話說: This method has

執行2程序的三種基本狀態及其轉換

程序的基本狀態: ①就緒(Ready)狀態 當程序已分配到除CPU以外的所有必要資源後,只要再獲得CPU,便可立即執行,程序這時的狀態就稱為就緒狀態。在一個系統中處於就緒狀態的程序可能有多個,通常將他們排成一個佇列,稱為就緒佇列。 ②執行狀態 程序已獲得CPU,其程式正在執行。在單處理機系統中,只有一

java執行深入理解volitale關鍵字

一、java記憶體模型與多執行緒程式設計中的三個感念 1、原子性 原子性是指一些操作或者全都執行,要麼或者全都不執行,整個操作作為一個整體是不可分割的,例如,一個銀行中有兩個賬戶A,B,現在要從A賬戶中轉賬500元到B賬戶,那麼一共可以分為兩個步驟:

深入應用C++11 筆記---執行

C++11 多執行緒 1. 執行緒的建立 使用執行緒函式或者函式物件,並且可以同時制定執行緒函式的引數 #include<thread> void func(){/*do something*/} int main() { std

Java執行執行與程序

1.執行緒和程序 1.1 程序 程序是作業系統的概念,我們執行的一個TIM.exe就是一個程序。 程序(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。在早期面向程序設計的計算機結構中,程序是程式的基本執行實體;在當代面向執行緒設計

Java執行Thread類

Thread類的例項方法 start() start方法內部會呼叫方法start方法啟動一個執行緒,該執行緒返回start方法,同時Java虛擬機器呼叫native start0啟動另一個執行緒呼叫run方法,此時有兩個執行緒並行執行; 我們來分析下start0方法,start0到底是如何呼叫run方法的

Java執行Synchronized

多執行緒安全 髒讀:多個執行緒對同一個物件的例項變數進行修改後訪問,導致讀到的資料是被修改過的。 例項 ThreadDomain16類 public class ThreadDomain16 { private int num = 0; public void addNum(String u

Java執行volatile

volatile volatile是一種輕量同步機制。請看例子 MyThread25類 public class MyThread25 extends Thread{ private boolean isRunning = true; public boolean isRunning()

Java執行死鎖

死鎖 概念 當執行緒Thread-0持有鎖Lock1,Thread-1持有鎖Lock2,此時Thread-0申請Lock2鎖的使用權,Thread-1申請Lock1鎖的使用權,Thread-0和Thread-1都在無限地等待鎖的使用權。這樣就造成了死鎖。 死鎖是主要由於設計的問題。一旦出現死鎖,死鎖的執行

Java執行ReentrantLock

加鎖和解鎖 我們來看下ReentrantLock的基本用法 ThreadDomain35類 public class ThreadDomain35 { private Lock lock = new ReentrantLock(); public void testMethod()

Java執行十三執行

執行緒池類結構 1.Executor是頂級介面,有一個execute方法。 2.ExecutorService介面提供了管理執行緒的方法。 3.AbstractExecutorService管理普通執行緒,SchedulerExecutorService管理定時任務。 簡單的示例 public class