1. 自己的一些感受
在我自己的知识库里面,一直是知道由ThreadLocal这么一个东西的,也知道这是一个通过线程隔离资源解决并发的,但是却对于使用场景和具体实现很模糊。
- 它是如何跟线程挂钩的?
- 在同一个线程中,有多个ThreadLocal又是如何存储的?
- 我们用它来解决什么问题呢?
2. 先聊并发导致线程不安全的原因
- 不能保证原子性
- 不能保证有序性
- 不能保证可见性
更重要的一个前提是,需要有竞态条件,多线程共享一个变量资源,如果这个前提条件都没有,就不存在所谓的并发安全性问题了。
3. ThreadLocal
- 从名字上来看,大致意思是”线程本地“变量;
- 本质上,ThreadLocal确实是每个线程独立拥有,不共享的。
- ThreadLocal保证并发安全是因为线程资源的隔离,导致并不具备竞态条件,所以天然的不会存在线程安全性问题。
通过查询源码我们可以看一下ThreadLocal这个类的结构:
-
静态内部类: ThreadLocalMap. 用于实际存储本地变量数据的
从这里面可以看出两点:1. 弱引用存储数据可能存在垃圾回收导致作为key的ThreadLocal对象回收,而value存在。这个时候始终存在一个强引用的value对象不会被gc回收,并且一直存在。这个时候累积多了就会导致内存泄漏(解决办法:使用完成后调用remove方法)。2. 一个线程可以有多个ThreadLocal,都会被存在Entry[]数组中。
-
ThreadLocal初始化赋值,与当前线程绑定
threadLocals作为Thread类中的一个成员变量,在ThreadLocal进行数据赋值初始化时会被赋值,这个时候其实就是将线程与实际数据变量进行了绑定。故,ThreadLocal中的数据都是线程隔离的,就不会存在多线程共享变量导致的并发问题。
子线程如何使用父线程的本地变量
InheritableThreadLocal实现了子线程使用父线程的变量
// 构造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. 应用场景
- 存储数据库连接对象:why?为了避免多个线程公用一个连接对象进行数据库操作时相互之间产生影响。你肯定不想自己查出来的某些结果被其他线程篡改过
- web服务,每次请求的url,参数等信息打印,并标准线程编号,便于后续排查实际问题。