ThreadLocal学习
1、简介:
类ThreadLocal<T>,为变量提供了线程本地化副本。对于用ThreadLocal维护的变量,当前线程中的副本不同于它在其他线程中的副本,每个线程通过ThreadLocal提供的get、set等方法来独立维护自己的变量副本。当多线程环境中的变量使用ThreadLocal维护时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。
ThreadLocal实例在类中通常作为静态私有化类变量。
2、ThreadLocal接口:
public T get(); //返回被维护变量的变量的线程本地化的副本值 public void set(T value); //将当前线程中维护的本地化副本值设置为value public void remove(); //移除当前线程中被维护变量的值
protected T initialValue(); //提供线程本地化对象的初始值
3、适用性:
ThreadLocal适用于为每个线程都提供一个固定的初始状态的情形,每个线程使用自己的本地化对象来完成自己的操作。
4、Demo:
对非线程安全对象的改造:
1 public class TopicDao{ 2 //一个非线程安全的变量 3 private Connection conn; 4 5 public void addTopic(){ 6 conn = DriverManager.getConnection(); 7 8 //在多线程环境中使用非线程安全变量 9 Statement stat = conn.createStatement(); 10 11 } 12 }
在使用JDBC来操作数据库时,Connection对象提供了所连接的数据库的表、支持的SQL语句等信息,但是Connection本身并不是一个线程安全变量,用于多线程环境中可能会出现问题。
改造: 使用ThreadLocal来为每个线程维护一个conn变量的线程本地化副本。
1 public class TopicDao{ 2 //使用ThreadLocal来保存Connection变量 3 private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); 4 public static Connection getConnection(){ 5 //如果connThreadLocal没有本线程对应的Connection,创建一个新的Connection, 6 //并将其保存到线程本地变量中 7 if(connThreadLocal.get() == null ){ 8 Connection conn = ConnectionManager.getConnection(); 9 connThreadLocal.set(conn); 10 return conn; 11 }else{ 12 //直接返回线程本地变量 13 return connThreadLocal.get(); 14 } 15 } 16 17 public void addTopic(){ 18 //从ThreadLocal中获取线程对象 19 Statement stat = getConnection().createStatement(); 20 } 21 }
ThreadLocal使用方式:
由例子中也可看出,ThreadLocal实例通常作为类的私有静态成员,并且提供一个静态方法来返回ThreadLocal所维护的线程本地化对象(该例中未conn)的值,同时在该静态方法中为每个线程提供该线程本地化对象的初始值。
5、ThreadLocal.java源文件:
1 /** 2 * ThreadLocal内部包含一个用数组实现的哈希表,用来存储对应到每个线程的局部对象的值 3 * 其中,ThreadLocal对象担当key,实际通过threadLocalHashCode值来进行检索 4 */ 5 public class ThreadLocal<T> { 6 /** 7 * 存储用于确定哈希表的键值 8 */ 9 private final int threadLocalHashCode = nextHashCode(); 10 11 /** 12 * 返回在当前线程中的线程局部对象的值, 13 * 若线程局部对象对于当前线程没有值,则被初始化微 initialValue方法的返回值 14 */ 15 public T get() { 16 Thread t = Thread.currentThread(); 17 ThreadLocalMap map = getMap(t); 18 if (map != null) { 19 ThreadLocalMap.Entry e = map.getEntry(this); 20 if (e != null) 21 return (T)e.value; 22 } 23 24 return setInitialValue(); 25 } 26 27 /** 28 * 获取当前线程对应的ThreadLocalMap对象 29 * 若存在,则将当前线程与T类型变量value按照名值对的方式放置到ThreadLocalMap中; 30 * 若不存在,则创建当前ThreadLocalMap对象,并放入 31 */ 32 public void set(T value) { 33 Thread t = Thread.currentThread(); 34 ThreadLocalMap map = getMap(t); 35 if (map != null) 36 //以当前线程对象为key,设置当前局部对象的值 37 map.set(this, value); 38 else 39 createMap(t, value); 40 } 41 42 /** 43 * 移除当前线程中所保存的本地化对象的值 44 */ 45 public void remove() { 46 ThreadLocalMap m = getMap(Thread.currentThread()); 47 if (m != null) 48 //从ThreadLocalMap中移除对象 49 m.remove(this); 50 } 51 52 /** 53 * 返回当前线程关联的ThreadLocalMap对象 54 * 查看Thread源码可知,每个Thread都含有ThreadLocal.ThreadLocalMap类型的threadLocals变量 55 */ 56 ThreadLocalMap getMap(Thread t) { 57 return t.threadLocals; 58 } 59 60 /** 61 * 创建当前线程关联的ThreadLocalMap对象 62 */ 63 void createMap(Thread t, T firstValue) { 64 t.threadLocals = new ThreadLocalMap(this, firstValue); 65 } 66 67 /** 68 * ThreadLocalMap是一个定制的只能用来存储线程局部对象的哈希映射表 69 * 70 * 使用数组来作为哈希表存储结构 71 * 72 * 其中数组中元素类型为Entry,Entry为指向ThreadLocal的弱引用的子类,并且含有Object变量来维护与当前ThreadLocal对象关联的变量值 73 * 74 */ 75 static class ThreadLocalMap { 76 77 static class Entry extends WeakReference<ThreadLocal> { 78 /** 79 * 维护与当前ThreadLocal相关联的变量值 80 */ 81 Object value; 82 83 Entry(ThreadLocal k, Object v) { 84 super(k); 85 value = v; 86 } 87 } 88 89 //哈希表的初始大小 90 private static final int INITIAL_CAPACITY = 16; 91 92 /** 93 * 哈希表存储的数组 94 * 95 * 其中数组中元素类型为Entry,Entry为指向ThreadLocal的弱引用的子类,并且含有Object变量来维护与当前ThreadLocal对象关联的变量值 96 */ 97 private Entry[] table; 98 99 /** 100 * ThreadLocalMap使用延迟初始化,当我们需要向ThreadLocalMap中放元素时,才会初始化它 101 */ 102 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { 103 table = new Entry[INITIAL_CAPACITY]; 104 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //产生哈希值 105 table[i] = new Entry(firstKey, firstValue); //初始时,使用ThreadLocal.threadLocalHashCode作为哈希表的哈希值 size = 1; 106 107 //设定ThreadLocalMap中元素个数 108 setThreshold(INITIAL_CAPACITY); 109 } 110 111 /** 112 * 设置于当前key所关联的线程本地化对象的值 113 * 114 * 涉及到哈希表的冲突处理 115 */ 116 private void set(ThreadLocal key, Object value) { 117 Entry[] tab = table; 118 int len = tab.length; 119 int i = key.threadLocalHashCode & (len-1); 120 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 121 ThreadLocal k = e.get(); 122 if (k == key) { 123 e.value = value; 124 return; 125 } 126 127 if (k == null) { 128 replaceStaleEntry(key, value, i); 129 return; 130 } 131 } 132 133 tab[i] = new Entry(key, value); 134 int sz = ++size; 135 if (!cleanSomeSlots(i, sz) && sz >= threshold) 136 rehash(); 137 } 138 139 /** 140 * 删除ThreadLocal对应的线程本地化对象的值 141 */ 142 private void remove(ThreadLocal key) { 143 Entry[] tab = table; 144 int len = tab.length; 145 int i = key.threadLocalHashCode & (len-1); 146 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 147 if (e.get() == key) { 148 e.clear(); 149 expungeStaleEntry(i); 150 return; 151 } 152 } 153 } 154 } 155 }
根据ThreadLocal.java中列出的源码,可以走一遍ThreadLocal是如何进行set、get、remove操作的,可以加深对ThreadLocal对象的理解和其内部工作机制。
注:查看Thread源码可知,每个Thread类中都含有ThreadLocal.ThreadLocalMap类型的threadLocals变量(初始值为null)。