1. 程式人生 > >ThreadLocal原理及使用示例

ThreadLocal原理及使用示例

lin res lap 對象 .html ng- try stat nbsp

簡介:本文已一個簡要的代碼示例介紹ThreadLocal類的基本使用方式,在此基礎上結合圖片闡述它的內部工作原理。

歡迎探討,如有錯誤敬請指正

如需轉載,請註明出處 http://www.cnblogs.com/nullzx/


1. ThreadLocal<T> 簡介和使用示例

ThreadLocal只有一個無參的構造方法

public ThreadLocal()

ThreadLocal的相關方法

public T get() 
public void set(T value) 
public void remove() 
protected T initialValue() 

initialValue方法的訪問修飾符是protected,該方法為第一次調用get方法提供一個初始值。默認情況下,第一次調用get方法返回值null。在使用時,我們一般會復寫ThreadLocal的initialValue方法,使第一次調用get方法時返回一個我們設定的初始值。

下面是一個ThreadLocal的一個簡單使用示例

package javalearning;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class ThreadLocalDemo {
	/*定義了1個ThreadLocal<Integer>對象,
	 *並復寫它的initialValue方法,初始值是3*/
	private ThreadLocal<Integer> tlA = new ThreadLocal<Integer>(){
		protected Integer initialValue(){
			return 3;
		}
	};
	
    /*	
    private ThreadLocal<Integer> tlB = new ThreadLocal<Integer>(){
		protected Integer initialValue(){
			return 5;
		}
	};
	*/
	
	/*設置一個信號量,許可數為1,讓三個線程順序執行*/
	Semaphore semaphore = new Semaphore(1);
	
	private Random rnd = new Random();
	
	/*Worker定義為內部類實現了Runnable接口,tlA定義在外部類中,
每個線程中調用這個對象的get方法,再調用一個set方法設置一個隨機值*/
	public class Worker implements Runnable{
		@Override
		public void run(){
			
			try {
				Thread.sleep(rnd.nextInt(1000)); /*隨機延時1s以內的時間*/
				semaphore.acquire();/*獲取許可*/
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			int valA = tlA.get();
			System.out.println(Thread.currentThread().getName() +" tlA initial val : "+ valA);
			valA = rnd.nextInt();
			tlA.set(valA);
			System.out.println(Thread.currentThread().getName() +" tlA  new     val: "+ valA);
			
			/*
			int valB = tlB.get();
			System.out.println(Thread.currentThread().getName() +" tlB initial val : "+ valB);
			valB = rnd.nextInt();
			tlA.set(valB);
			System.out.println(Thread.currentThread().getName() +" tlB 2    new val: "+ valB);
            */
			
			semaphore.release();
			
			/*在線程池中,當線程退出之前一定要記得調用remove方法,因為在線程池中的線程對象是循環使用的*/
			tlA.remove();
			/*tlB.remove();*/
		}
	}
	
	/*創建三個線程,每個線程都會對ThreadLocal對象tlA進行操作*/
	public static void main(String[] args){
		ExecutorService es = Executors.newFixedThreadPool(3);
		ThreadLocalDemo tld = new ThreadLocalDemo();
		es.execute(tld.new Worker());
		es.execute(tld.new Worker());
		es.execute(tld.new Worker());
		es.shutdown();
	}
}

運行結果

pool-1-thread-1 tlA initial val : 3
pool-1-thread-1 tlA  new     val: -1288455998
pool-1-thread-3 tlA initial val : 3
pool-1-thread-3 tlA  new     val: 112537197
pool-1-thread-2 tlA initial val : 3
pool-1-thread-2 tlA  new     val: -12271334

從運行結果可以看出,每個線程第一次調用TheadLocal對象的get方法時都得到初始值3,註意我們上面的代碼是讓三個線程順序執行,顯然從運行結果看,pool-1-thread-1線程結束後設置的新值,對pool-1-thread-3線程是沒有影響的,pool-1-thread-3線程完成後設置的新值對pool-1-thread-2線程也沒有影響。這就仿佛把ThreadLocal對象當做每個線程內部的對象一樣,但實際上tlA對象是個外部類對象,內部類Worker訪問到的是同一個tlA對象,也就是說是被各個線程共享的。這是如何做到的呢?我們現在就來看看ThreadLocal對象的內部原理。

2. ThreadLocal<T>的原理

首先,在Thread類中定義了一個threadLocals,它是ThreadLocal.ThreadLocalMap對象的引用,默認值是null。ThreadLocal.ThreadLocalMap對象表示了一個以開放地址形式的散列表。當我們在線程的run方法中第一次調用ThreadLocal對象的get方法時,會為當前線程創建一個ThreadLocalMap對象。也就是每個線程都各自有一張獨立的散列表,以ThreadLocal對象作為散列表的key,set方法中的值作為value(第一次調用get方法時,以initialValue方法的返回值作為value)。顯然我們可以定義多個ThreadLocal對象,而我們一般將ThreadLocal對象定義為static類型或者外部類中。上面所表達的意思就是,相同的key在不同的散列表中的值必然是獨立的,每個線程都是在各自的散列表中執行操作

技術分享

TheadLocal中的get源代碼

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//這裏的this是指當前的ThreadLocal對象
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

3. 參考內容

[1]. Java並發編程:深入剖析ThreadLocal

[2]. [Java並發包學習七]解密ThreadLocal

ThreadLocal原理及使用示例