ThreadLocal的意义和实现
可以想像,如果一个对象的可变的变量被多个线程访问时,必然是不安全的。
在单线程应用可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都传递一个Connection对象。ThreadUnsafe类就是这样做的:
public class ThreadUnsafe { private static Connection connection = DriverManager.getConnection(DB_URL); public void Connection getConnection{ /* 在多线程应用中,connection 在被多个线程访问 */ return connection; } }
但是JDBC连接对象不一定是线程安全的,在多个线程访问到Connection时,就可能出现安全问题。为了解决这个问题,ThreadLocal类提供了安全的做法。
通过将JDBC的Connection对象封装在ThreadLocal对象中,当每个线程访问需要Connection对象时,ThreadLocal对象返回的是一个副本。
public class ThreadUnsafe { private static ThreadLocal<Connection> connectionHodler = new ThreadLocal<>{ public Connection initialValue() { return DriverManager.getConnection(DB_URL); } } public void Connection getConnection{ /* 即使多个线程可以访问,依然安全 */ return connectionHolder.get(); } }
ThreadLocal是如何实现这种功能?
首先,在Thread类中有一个threadLocals的实例变量,这是一个Map,保存了与线程相关的ThreadLocal对象封装的变量。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
当线程初次调用ThreadLocal对象的get方法时,就会调用initialValue()来获取初始值。
/** * 返回ThreadLocal封装的对象。*/ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { /* 首次调用map为null */ ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); /* 首次调用的返回值 */ } /** * 初始化封装在ThreadLocal中对象的值。*/ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); //为什么键值是ThreadLocal对象?,因为一个线程对象可能有使用多个ThreadLocal封闭的变量 else createMap(t, value); return value; } /** * 更新封装在ThreadLocal中对象的值*/ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * 创建一个Map,用于保存ThreadLocal和其封装的对象。*/ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
注意:ThreadLocalMap在ThreadLocal类中声明,却是在Thread类中使用的,原因在于,当线程结束时,这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收。
ThreadLocal类实现的是一种线程封闭技术。将变量封闭在单线程中,从而避免同步。
参考: 《Java Concurrency in Practice》 P35&P37
一年365天,每天进步一点点。