背景
我們都知道ThreadLocal可以解決線程安全問題,它會把共享變量copy一份副本到線程空間中。那么它是怎么做到的,我們又應該如何使用它呢?
Thread Local 是什么
本地變量副本:
它不是一個線程,而是線程的一個本地化對象。當工作于多線程中的對象使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程分配一個獨立的變量副本。所以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中“Local”所要表達的意思。
Thread Local背后的實現機制-原理
1.每個線程都有一個ThreadLocalMap變量,所以可以想到每個線程獨立的變量都是通過ThreadLocalMap數據結構來維護的。
//Thread.java 中
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
/**
* 1.獲取本地線程 thread.currentThread();
* 2.取得本地線程的threadLocalMap,然后往這個map里塞值。
*/
public void set(T value) {
Thread t = Thread.currentThread(); //從這里可以看出代碼執行這里,就會往當前線程設置變量。
ThreadLocalMap map = getMap(t);
if (map != null)
// ThreadLocalMap(ThreadLocal,Object),以ThreadLocal作為key,object作為value。
map.set(this, value);
else
createMap(t, value);
}
/**
*
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
如何使用Thread Local
了解了ThreadLocal的使用原理,我們先看ThreadLocal類有哪些方法。
initValue()方法
set()方法
get()方法
ThreadLocal{
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)
return (T)e.value;
}
return setInitialValue();
}
}
我們定義如下threadLocal變量。
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();
private ThreadLocal myThreadLocal2 = new ThreadLocal<String>();
在實際使用過程中
myThreadLocal1.get(),獲取的是當前線程中threadLocalMap 里myThreadLocal1為key對應的值。
當調用myThreadLocal1.set("abc"),自動的把abc設置到當前Thread.ThreadLocalMap()中去了,參考ThreadLocal類型set()方法。
注意我們在Thread中添加的ThreadLocal.ThreadLocalMap 變量在線程結束的時候必須釋放掉,請查看Thread類exits方法:
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
threadLocal 與thread的同步鎖比較
threadLocal 是為每個線程維護一份本地的變量,所以是“空間換時間”的方式。
thread同步鎖 訪問串行化、對象共享化,是“時間換空間”的方式。
如何正確使用threadLocal
由于threadLocal就是解決多個線程之間互相干擾的情況,每個線程都有他們自己的threadLocalMap數據結構。
所以各個線程的threadLocalMap不能指向同一個引用,即每個線程需要肚子引用記錄自己的對象threadLocalMap.set(this,new Object())這樣線程之間才不會干擾。
/**
* 由于是thread中有一個threadLocal.threadlocalMap引用,所以
* @author shawn
*
*/
public class SafeThreadLocalThread implements Runnable{
private ThreadLocal myThreadLocal = new ThreadLocal<Number>();
public static int i = 0;
@Override
public void run() {
Number number = new Number();//每個線程需要使用自己的對象。
number.setNo(i++);
//將值存儲到threadLocal中
myThreadLocal.set(number);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("number.getNo()" + number.getNo());
}
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
newCachedThreadPool.execute(new SafeThreadLocalThread());
}
}
/**
* 每個number 各不影響因為他是在各個線程里單獨new的。
* number.getNo()2
*number.getNo()3
*number.getNo()1
*number.getNo()4
*number.getNo()0
*/
}
詳細代碼請查看:https://github.com/shawnxjf1/J2seCodeExample
不同級別的緩存
1.threadLocal相當于線程級別的緩存,每個線程綁定一個map容器在這個容器里存儲該線程里的數據。
2.而我們在代碼中經常用到static 變量的緩存,比如如下代碼:
class demo {
public static Map<String,Object> demoCache= new Hashmap();//定義jvm級別的緩存
}
3.對于tomcat servlet container,j2ee container 每個容器都有自己的classloader,比如每個tomcat每個應用都有自己的appclassLoader只加載自己的class.
比如如下應用app1,app2可能需要緩存的數據是不一樣的,我們希望操作app1 的demoCache時候不影響應用app2的。
此時我們需要的緩存級別就是按容器級別(每個容器的classloader不一樣),最常用到的容器級別的工具是:
org.apache.commons.beanutils.ContextClassLoaderLocal{
private final Map<ClassLoader, T> valueByClassLoader = new WeakHashMap<ClassLoader, T>();
}
在apache beanUtils組建注冊convert的時候都是放在per(thread) context classloader級別的緩存里。
當然如果上述代碼運行在非容器(意味著只有一個classLoader),其效果就相當于static 變量緩存了。
相關代碼見參考列 [beanutils注冊convert]。
寫完后想法
以前使用ThreadLocal 都是從網上摘抄代碼片段,但是當我弄明白ThreadLocal原理自己就能夠靈活使用ThreadLocal代碼來。弄明白了原理后你就是從腦子出發來表達和實現ThreadLocal信息,而如果你只是copy網上代碼說明信息是沒有經過腦子的,當然我們不要求所有的技術都弄得很深但是你使用它的時候一定要知道它與其他模塊或代碼對接的原理。
參考
參考1:beanUtils 注冊convert
//ContextClassLoaderLocal 類似于threadLocal
BeanUtilsBean{
private static final ContextClassLoaderLocal<BeanUtilsBean>
BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>() {
// Creates the default instance used when the context classloader is unavailable
@Override
protected BeanUtilsBean initialValue() {
return new BeanUtilsBean();
}
};
/**
* 獲取當前classloader的BeanutilsBean
* Gets the instance which provides the functionality for {@link BeanUtils}.
* This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
* This mechanism provides isolation for web apps deployed in the same container.
*
* @return The (pseudo-singleton) BeanUtils bean instance
*/
public static BeanUtilsBean getInstance() {
return BEANS_BY_CLASSLOADER.get();
}
/**
* Sets the instance which provides the functionality for {@link BeanUtils}.
* This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
* This mechanism provides isolation for web apps deployed in the same container.
*
* @param newInstance The (pseudo-singleton) BeanUtils bean instance
*/
public static void setInstance(final BeanUtilsBean newInstance) {
BEANS_BY_CLASSLOADER.set(newInstance);
}
}
ConvertUtilsBean{
protected static ConvertUtilsBean getInstance() {
return BeanUtilsBean.getInstance().getConvertUtils();
}
}