Threadlocal是什么
java中处理多线程资源共享主要有两种方法,一种是同步,另一种就是使用threadlocal,锁机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问。而Threadlocal采用了“以空间换时间”的方式,为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map(ThreadLocalMap),用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
Threadlocal简单实现
按照上面描述的原理,我们可以实现一个简单的threadlocal如下:
public class SimpleThreadLocal {
private Map<Thread, Object> map = new ConcurrentHashMap();
public void set(Object o) {
//键为线程对象,值为本线程的变量副本
map.put(Thread.currentThread(), o);
}
public Object get() {
Thread currentThread = Thread.currentThread();
//返回本线程对应的变量
Object value = map.get(currentThread);
if (value == null && !map.containsKey(currentThread)) {
//如果在Map中不存在,放到Map中保存起来。
value = initialValue();
map.put(currentThread, value);
}
return value;
}
public void remove() {
map.remove(Thread.currentThread());
}
private Object initialValue() {
return null;
}
}
这个Threadlocal虽然有点幼稚,但是已经和JDK的Threadlocal很接近了。
ThreadLocal底层实现
那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?下面我们分析一下ThreadLocal的具体实现细节,首先展示了ThreadLocal提供的一些方法,我们重点关注的是get、set、remove方法。
构造函数
public ThreadLocal() {
}
很遗憾构造函数什么都没做,那么初始化阶段势必在set操作时完成。
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,也就是说每个线程都有自己独立的ThreadLocalMap对象
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。
上面代码出现的ThreadLocalMap是什么,?
ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。
ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
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();
}
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;
}
protected T initialValue() {
return null;
}
在获取和当前线程绑定的值时,ThreadLocalMap对象以this指向的ThreadLocal对象为键进行查找,这和前面set()方法的代码相互呼应。如果不存在就返回默认的null值,initialValue方法可以在初始化时被重写,从而实现自定义的默认值。
进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?
因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
清除当前线程的ThreadLocal
InheritableThreadLocal
ThreadLocal固然很好,但是子线程并不能取到父线程的ThreadLocal的变量。使用InheritableThreadLocal时可以做到的在父子线程之间传递数据。
如下demo:
public class InheritableThreadLocalDemo {
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
private static InheritableThreadLocal<Integer> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
integerThreadLocal.set(1001); // father
inheritableThreadLocal.set(1002); // father
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ integerThreadLocal.get() + "/"
+ inheritableThreadLocal.get())).start();
}
}
//output Thread-0:null/1002
InheritableThreadLocal继承至Threadlocal,重写了childValue、getMap、createMap方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
可以看出InheritableThreadLocal使用的Map是Thread中inheritableThreadLocals变量!我们看下在Thread初始化过程中会对该变量进行的操作
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//大概就是将父线程的ThreadLocalMap复制到自己的ThreadLocalMap里面来,这样我们就可以使用InheritableThreadLocal访问到父线程中的变量了
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
ThreadLocal使用例子
ThreadLocal实现的可复用的耗时统计工具Profiler
/**
* @author perist
* @date 2016/12/6
* @time 21:36
*/
public class Profiler {
private static final ThreadLocal<Long> durationThreadLocal = new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
public static void begin() {
durationThreadLocal.set(System.currentTimeMillis());
}
public static long end() {
return System.currentTimeMillis() - durationThreadLocal.get();
}
public static void main(String[] args) {
Profiler.begin();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Profiler.end());
}
}
ThreadLocal实现数据库连接线程隔离
/**
* @author perist
* @date 2016/12/6
* @time 23:17
*/
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "username",
"password");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
public static void setConnection(Connection conn) {
connectionHolder.set(conn);
}
}
通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是和当前线程绑定的那个Connection对象,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。
在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。
关于GC
ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉。所以不用担心内存泄漏的问题。
Thread的exit()方法做了大量帮助GC的操作:
/**
* 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缺陷
ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。
如果多个线程并发访问的对象实例只允许,也只能创建那么一个(单例),那就没有别的办法了,老老实实的使用同步机制吧。