前言
保证线程安全的一个方法是线程封闭,而ThreadLocal关键字就能实现这一点,用ThreadLocal实现的变量会和线程绑定,每个线程对应自己的一份数据,自然就产生不了竞争资源问题。
使用方式
public class ThreadLocalTest {
public static ThreadLocal<String> itl = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return new Date().toString();
}
@Override
protected String childValue(String parentValue) {
return parentValue + " hello";
}
};
public static ThreadLocal<String> it2 = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return new Date().toString();
}
};
public static void main(String[] args) throws InterruptedException {
itl.get();//1
for(int i =0;i<5;i++) {//2
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : " + itl.get());
}
}).start();
}
for(int i =0;i<5;i++) {//3
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : " + it2.get());
}
}).start();
}
}
}
运行结果
Thread-0 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-1 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-2 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-3 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-4 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-5 : Tue Apr 18 00:23:23 CST 2017
Thread-6 : Tue Apr 18 00:23:24 CST 2017
Thread-7 : Tue Apr 18 00:23:25 CST 2017
Thread-8 : Tue Apr 18 00:23:26 CST 2017
Thread-9 : Tue Apr 18 00:23:27 CST 2017
代码讲解
这里面有2个类,ThreadLocal和InheritableThreadLocal,从字面意思上就能看出来第二个类具有继承的功能。
这边我在threadlocal里面初始化了一个date,如果和线程相关,每次输出的时间应该是不同的。
在代码块3,我开了5个线程去输出it2的值,因为it2是线程相关的,所以输出的5个值,应该是不同的。看后面5个输出。
而InheritableThreadLocal是线程继承的,在代码块1,也就是main线程,我把it1初始化了,而在之后5个子线程中,会继承的使用父线程的值,所以从输出可以看到,5个输出都是一样的。没有因为sleep而改变。
如果把代码1注释掉呢,大家可以试下,就能领略到这个特性。
源码讲解
ThreadLocal是怎么和线程绑定的
首先我们可以在ThreadLocal的源码内看到2个Map
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
使用了ThreadLocal或InheritableThreadLocal包装的数据会分别保存到Thread内部的2个map中去,map的key是当前的threadlocal对象。
保存的动作以及map的初始化在调用get方法中实现
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
很明显的看到,会在当前线程的map中去拿value,如果不存在,执行创建map,已经调用我们自己实现的initialValue方法,插入map并且返回。这边也有个乐观锁的判断。
InheritableThreadLocal类似,只不过他重载了getMap和createMap2个方法,设置到可继承的map中去
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
InheritableThreadLocal是怎么实现让子线程继承
做到这个机制,我们要去看线程的构造函数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
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);
//如果父线程存在inheritableThreadLocals复制到自己的Thread对象内
if (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();
}
因此如果父线程初始化了inheritableThreadLocals ,子线程可以用同一个InheritableThreadLocal作为key去拿到value
ThreadLocal内部Map机制
作为一个map,肯定有entry,ThreadLocal的entry有点不同
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
弱引用,因此,当这个对象没被引用的时候,会被垃圾回收
从entry的内部结构也可以看出来,与传统的hashmap不同,这个内部map解决冲突的方式,并不是使用链表,而是直接循环放到下一个桶
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
//这边是如果这个key被回收了,会把后面的数据往上推,保持冲突的key的数据连续
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
spring中ThreadLocal运用
正在努力攻读spring源码,下面这2块是我想解决的疑惑
- springmvc的请求都是基于线程的
- spring的事务嵌套