1. 程式人生 > >ThreadLocal實現執行緒範圍的共享變數

ThreadLocal實現執行緒範圍的共享變數

這裡先說下ThreadLocal不是一個執行緒的本地實現版本,不是一個Thread,它是thread local variable(執行緒區域性變數);用於實現執行緒內的資料共享,即對於相同的程式程式碼,多個模組在同一個執行緒中執行時要共享一份資料,而在另外執行緒中執行時又共享另外一份資料。換一句話說就是為每一個使用該變數的執行緒都提供一個變數值的副本,是每一個執行緒都可以獨立地改變自己的副本,而不會和其它執行緒的副本衝突。從執行緒的角度看,就好像每一個執行緒都完全擁有該變數

現做一個小練習,定義一個全域性共享的ThreadLocal變數,然後啟動五個執行緒向該ThreadLocal變數中儲存一個隨機值,接著各個執行緒呼叫另外其他多個類的方法,這多個類的方法中讀取這個

ThreadLocal變數的值,就可以看到多個類在同一個執行緒中共享同一份資料。

定義兩個類TestA TestB,用於獲取值MyThreadLocal類中的X

TestA:

package com.study.threadlocal;

/**
 * 
 * @ClassName: TestA
 * @Description: 用於獲取 MyThreadLocal 中變數x的值
 * @author 我夕

 */
public class TestA {
	
	public void print(){
		System.out.println(Thread.currentThread()+": TestA ,x current value is:"+MyThreadLocal.getInstance().getX());
	}
}



TestB:

package com.study.threadlocal;
/**
 * 
 * @ClassName: TestB
 * @Description: 用於獲取 MyThreadLocal 中變數x的值
 * @author 我夕
 */
public class TestB {
	public void print(){
		System.out.println(Thread.currentThread()+": TestB ,x current value is:"+MyThreadLocal.getInstance().getX());
	}
}



MyThreadLocal:

package com.study.threadlocal;
/**
 * 
 * @ClassName: MyThreadLocal
 * @Description: TODO
 * @author 我夕
 */
public class MyThreadLocal {
	
	private Integer x;
	
	//將構造器宣告有私有的,避免外部建立他
	private MyThreadLocal(){}
	
	private static ThreadLocal instanceThreadLocal=new ThreadLocal();//new ThreadLocal
	
	public static MyThreadLocal getInstance(){
		MyThreadLocal instance=(MyThreadLocal)instanceThreadLocal.get();
		if(instance==null){
			instance=new MyThreadLocal();
			instanceThreadLocal.set(instance);
		}
		return instance;
	}

	public Integer getX() {
		return x;
	}

	public void setX(Integer x) {
		this.x = x;
	}
	//除去執行緒的方法
	public static void remove(){
		instanceThreadLocal.remove();
	}

}



ThreadLocalTest

package com.study.threadlocal;

import java.util.Random;

/**
 * 
 * @ClassName: ThreadLocalTest
 * @Description: 執行緒區域性變數練習
 * @author 我夕
 */
public class ThreadLocalTest {
	
	public static void main(String[] args) {
		
		final TestA testA=new TestA();
		final TestB testB=new TestB();
		
		//建立5個執行緒
		for(int i=0;i<5;i++){
			Thread thread=new Thread(new Runnable() {
				
				@Override
				public void run() {
					//生成一個隨機數字給x
					MyThreadLocal.getInstance().setX(new Random().nextInt(100));
					System.out.println(Thread.currentThread()+",X current value is:"+MyThreadLocal.getInstance().getX());
					testA.print();
					testB.print();
					MyThreadLocal.getInstance().remove();

				}
			});
			thread.setName("thread_"+i);
			thread.start();
		}
		
	}
}


執行

從以上可以看出,TestA TestB確實在同一個執行緒中共享同一份資料。

接下來,分析下 ThreadLocal set()get()方法原始碼

set原始碼:

     /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    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原始碼

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

set()get()方法,我想大家應該都知道其的作用了,set設定當前執行緒的執行緒區域性變數副本的值,get返回當前執行緒的執行緒區域性變數副本的值,其set方法相當於往其內部的map中增加一條記錄,key分別是各自的執行緒,value是各自的set方法傳進去的值,線上程結束時可以呼叫ThreadLocal.clear()方法,這樣會更快釋放記憶體,不呼叫也可以,因為執行緒結束後也可以自動釋放相關的ThreadLocal變數。