1. 自己的一些感受
在我自己的知識庫里面,一直是知道由ThreadLocal這么一個東西的荠列,也知道這是一個通過線程隔離資源解決并發(fā)的刽酱,但是卻對于使用場景和具體實現(xiàn)很模糊。
- 它是如何跟線程掛鉤的邮偎?
- 在同一個線程中管跺,有多個ThreadLocal又是如何存儲的?
- 我們用它來解決什么問題呢禾进?
2. 先聊并發(fā)導致線程不安全的原因
- 不能保證原子性
- 不能保證有序性
- 不能保證可見性
更重要的一個前提是豁跑,需要有競態(tài)條件,多線程共享一個變量資源泻云,如果這個前提條件都沒有艇拍,就不存在所謂的并發(fā)安全性問題了。
3. ThreadLocal
- 從名字上來看宠纯,大致意思是”線程本地“變量卸夕;
- 本質(zhì)上,ThreadLocal確實是每個線程獨立擁有婆瓜,不共享的快集。
- ThreadLocal保證并發(fā)安全是因為線程資源的隔離,導致并不具備競態(tài)條件廉白,所以天然的不會存在線程安全性問題个初。
通過查詢源碼我們可以看一下ThreadLocal這個類的結構:
ThreadLocal Structure
-
靜態(tài)內(nèi)部類: ThreadLocalMap. 用于實際存儲本地變量數(shù)據(jù)的
ThreadLocalMap
從這里面可以看出兩點:1. 弱引用存儲數(shù)據(jù)可能存在垃圾回收導致作為key的ThreadLocal對象回收,而value存在猴蹂。這個時候始終存在一個強引用的value對象不會被gc回收院溺,并且一直存在。這個時候累積多了就會導致內(nèi)存泄漏(解決辦法:使用完成后調(diào)用remove方法)磅轻。2. 一個線程可以有多個ThreadLocal珍逸,都會被存在Entry[]數(shù)組中。
-
ThreadLocal初始化賦值瓢省,與當前線程綁定
初始化賦值
綁定對應線程
threadLocals作為Thread類中的一個成員變量弄息,在ThreadLocal進行數(shù)據(jù)賦值初始化時會被賦值,這個時候其實就是將線程與實際數(shù)據(jù)變量進行了綁定勤婚。故摹量,ThreadLocal中的數(shù)據(jù)都是線程隔離的,就不會存在多線程共享變量導致的并發(fā)問題。
子線程如何使用父線程的本地變量
InheritableThreadLocal實現(xiàn)了子線程使用父線程的變量
// 構造Thread對象時缨称,初始化方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//inheritThreadLocals變量賦值凝果,將父線程的ThreadLocalMap對象賦值給inheritThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
4. 應用場景
- 存儲數(shù)據(jù)庫連接對象:why?為了避免多個線程公用一個連接對象進行數(shù)據(jù)庫操作時相互之間產(chǎn)生影響睦尽。你肯定不想自己查出來的某些結果被其他線程篡改過
- web服務器净,每次請求的url,參數(shù)等信息打印当凡,并標準線程編號山害,便于后續(xù)排查實際問題。