背景
我們都知道ThreadLocal可以解決線程安全問題,它會(huì)把共享變量copy一份副本到線程空間中。那么它是怎么做到的,我們又應(yīng)該如何使用它呢或南?
Thread Local 是什么
本地變量副本:
它不是一個(gè)線程,而是線程的一個(gè)本地化對(duì)象艾君。當(dāng)工作于多線程中的對(duì)象使用ThreadLocal維護(hù)變量時(shí)采够,ThreadLocal為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本。所以每一個(gè)線程都可以獨(dú)立地改變自己的副本腻贰,而不會(huì)影響其他線程所對(duì)應(yīng)的副本吁恍。從線程的角度看,這個(gè)變量就像是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思冀瓦。
Thread Local背后的實(shí)現(xiàn)機(jī)制-原理
1.每個(gè)線程都有一個(gè)ThreadLocalMap變量伴奥,所以可以想到每個(gè)線程獨(dú)立的變量都是通過ThreadLocalMap數(shù)據(jù)結(jié)構(gòu)來維護(hù)的。
//Thread.java 中
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
/**
* 1.獲取本地線程 thread.currentThread();
* 2.取得本地線程的threadLocalMap翼闽,然后往這個(gè)map里塞值拾徙。
*/
public void set(T value) {
Thread t = Thread.currentThread(); //從這里可以看出代碼執(zhí)行這里,就會(huì)往當(dāng)前線程設(shè)置變量感局。
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對(duì)象
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
}
我們定義如下threadLocal變量。
private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();
private ThreadLocal myThreadLocal2 = new ThreadLocal<String>();
在實(shí)際使用過程中
myThreadLocal1.get(),獲取的是當(dāng)前線程中threadLocalMap 里myThreadLocal1為key對(duì)應(yīng)的值撑毛。
當(dāng)調(diào)用myThreadLocal1.set("abc"),自動(dòng)的把a(bǔ)bc設(shè)置到當(dāng)前Thread.ThreadLocalMap()中去了书聚,參考ThreadLocal類型set()方法。
注意我們?cè)赥hread中添加的ThreadLocal.ThreadLocalMap 變量在線程結(jié)束的時(shí)候必須釋放掉藻雌,請(qǐng)查看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 是為每個(gè)線程維護(hù)一份本地的變量雌续,所以是“空間換時(shí)間”的方式。
thread同步鎖 訪問串行化胯杭、對(duì)象共享化驯杜,是“時(shí)間換空間”的方式。
如何正確使用threadLocal
由于threadLocal就是解決多個(gè)線程之間互相干擾的情況做个,每個(gè)線程都有他們自己的threadLocalMap數(shù)據(jù)結(jié)構(gòu)鸽心。
所以各個(gè)線程的threadLocalMap不能指向同一個(gè)引用,即每個(gè)線程需要肚子引用記錄自己的對(duì)象threadLocalMap.set(this,new Object())這樣線程之間才不會(huì)干擾居暖。
/**
* 由于是thread中有一個(gè)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();//每個(gè)線程需要使用自己的對(duì)象。
number.setNo(i++);
//將值存儲(chǔ)到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());
}
}
/**
* 每個(gè)number 各不影響因?yàn)樗窃诟鱾€(gè)線程里單獨(dú)new的膝但。
* number.getNo()2
*number.getNo()3
*number.getNo()1
*number.getNo()4
*number.getNo()0
*/
}
詳細(xì)代碼請(qǐng)查看:https://github.com/shawnxjf1/J2seCodeExample
不同級(jí)別的緩存
1.threadLocal相當(dāng)于線程級(jí)別的緩存,每個(gè)線程綁定一個(gè)map容器在這個(gè)容器里存儲(chǔ)該線程里的數(shù)據(jù)谤草。
2.而我們?cè)诖a中經(jīng)常用到static 變量的緩存跟束,比如如下代碼:
class demo {
public static Map<String,Object> demoCache= new Hashmap();//定義jvm級(jí)別的緩存
}
3.對(duì)于tomcat servlet container,j2ee container 每個(gè)容器都有自己的classloader,比如每個(gè)tomcat每個(gè)應(yīng)用都有自己的appclassLoader只加載自己的class.
比如如下應(yīng)用app1,app2可能需要緩存的數(shù)據(jù)是不一樣的丑孩,我們希望操作app1 的demoCache時(shí)候不影響應(yīng)用app2的冀宴。
此時(shí)我們需要的緩存級(jí)別就是按容器級(jí)別(每個(gè)容器的classloader不一樣),最常用到的容器級(jí)別的工具是:
org.apache.commons.beanutils.ContextClassLoaderLocal{
private final Map<ClassLoader, T> valueByClassLoader = new WeakHashMap<ClassLoader, T>();
}
在apache beanUtils組建注冊(cè)convert的時(shí)候都是放在per(thread) context classloader級(jí)別的緩存里温学。
當(dāng)然如果上述代碼運(yùn)行在非容器(意味著只有一個(gè)classLoader)略贮,其效果就相當(dāng)于static 變量緩存了。
相關(guān)代碼見參考列 [beanutils注冊(cè)convert]。
寫完后想法
以前使用ThreadLocal 都是從網(wǎng)上摘抄代碼片段逃延,但是當(dāng)我弄明白ThreadLocal原理自己就能夠靈活使用ThreadLocal代碼來览妖。弄明白了原理后你就是從腦子出發(fā)來表達(dá)和實(shí)現(xiàn)ThreadLocal信息,而如果你只是copy網(wǎng)上代碼說明信息是沒有經(jīng)過腦子的揽祥,當(dāng)然我們不要求所有的技術(shù)都弄得很深但是你使用它的時(shí)候一定要知道它與其他模塊或代碼對(duì)接的原理讽膏。
參考
參考1:beanUtils 注冊(cè)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();
}
};
/**
* 獲取當(dāng)前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();
}
}