ThreadLocal是用來保存線程的本地變量,能夠保證每一個線程都有一個本身的變量(包括static變量)。git
本文全部代碼請點擊我github
1 看個實際場景。安全
咱們要設計一個序列號生成器,每一個線程之間對序列號的獲取是是隔離的。初始咱們可能會這樣設計。使用一個static變量。多線程
首先有一個序列號生成器的接口ide
package ThreadLocal; /* *2016年8月28日 下午2:48:17 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ public interface NumberConstruct { public int get(); }
生成器的具體實現是:.net
package ThreadLocal; /* *2016年8月28日 下午2:49:34 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ public class ConcreteNumberConstructA implements NumberConstruct{ private volatile static int n=0; @Override public synchronized int get() { return ++n; } }
客戶端:線程
package ThreadLocal; /* *2016年8月28日 下午2:46:10 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ public class Test { //不使用ThreadLocal private static NumberConstruct numberConstruct=new ConcreteNumberConstructA();; //使用ThreadLocal //private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();; public static void main(String[] args){ //每一個線程獲取三個序列號 Runnable task=new Runnable() { public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName()+" "+numberConstruct.get()); } } }; //開啓是哪一個線程 Thread t1=new Thread(task); Thread t2=new Thread(task); Thread t3=new Thread(task); t1.start(); t2.start(); t3.start(); } }
結果;設計
能夠看到3個線程之間都共享了static變量(沒有考慮到共享資源的線程安全),這並非咱們想要的結果。code
因此咱們用ThreadLocal解決:
生成器的具體實現:
package ThreadLocal; /* *2016年8月28日 下午2:49:34 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ public class ConcreteNumberConstructB implements NumberConstruct{ private static ThreadLocal<Integer> n=new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; }}; @Override public int get() { n.set(n.get()+1); return n.get(); } }
客戶端中將
//不使用ThreadLocal private static NumberConstruct numberConstruct=new ConcreteNumberConstructA();
替換爲
//使用ThreadLocal private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();
其它均不變
結果:
這是咱們想要的結果。能夠看到對於每一個共享變量,每一個線程之間都有本身的副本,線程之間是隔離的。
2 實現咱們本身的ThreadLocal。
ThreadLocal內部其實很是簡單。主要是一個同步的HashMap(由於涉及到多線程共享資源),主要有如下幾個方法;
//獲得當前線程的副本值 get() //設定當前線程的副本值 set() //刪除當前線程的副本值 remove() //初始化當前線程的副本值 initialValue()
code;
MyThreadLocal類
package ThreadLocal; /* *2016年8月28日 下午3:57:17 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ import java.util.concurrent.ConcurrentHashMap; public class MyThreadLocal<T> { private ConcurrentHashMap<Thread, T> map=new ConcurrentHashMap<>(); //initialValue() protected T initialValue(){ //返回null,由子類指定初始值 return null; } //set() public void set(T value){ map.put(Thread.currentThread(), value); } //get() public T get(){ if(!map.containsKey(Thread.currentThread())){ T value=initialValue(); map.put(Thread.currentThread(), value); } return map.get(Thread.currentThread()); } //remove() public void remove(){ map.remove(Thread.currentThread()); } }
ConcreteNumberConstructC 類
package ThreadLocal; /* *2016年8月28日 下午2:49:34 *@Author Pin-Wang *@E-mail 1228935432@qq.com */ public class ConcreteNumberConstructC implements NumberConstruct{ private MyThreadLocal<Integer> n=new MyThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 0; }}; @Override public int get() { n.set(n.get()+1); return n.get(); } }
將客戶端中的
//使用ThreadLocal private static NumberConstruct numberConstruct=new ConcreteNumberConstructB();
替換爲
//使用本身的MyThreadLocal private static NumberConstruct numberConstruct=new ConcreteNumberConstructC();
結果:
總結:若是你須要多個線程之間共享變量的時候,想下是否須要考慮線程安全的問題,若是須要則能夠使用ThreadLocal簡單解決。