ThreadLocal
每个线程内都有一个自己的ThreadLocalMap
类型的成员变量
// Thread类所维护的
// java.lang.Thread#threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
// java.lang.ThreadLocal.ThreadLocalMap
static class ThreadLocalMap {
// ThreadLocal对象和值value被封装为了Entry对象
// ThreadLocal在没有外部强引用时,发生GC时会被回收
// GC时会使弱引用Entry关联的ThreadLocal对象释放,但value因为是强引用,并不会释放,Entry对象也不会释放
// 此时: Entry对象.get()为null,但是Entry对象.value还是有值的
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table; // 初始容量16,扩容因子是2/3
// java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 对应的hash索引值
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 相当于修改值
e.value = value;
return;
}
}
tab[i] = new Entry(key, value); // 将ThreadLocal对象和value封装为Entry对象存入Entry数组中
}
}
// 释放value的时机:
// 1. entry对象.get()时发现为null,即发现关联的ThreadLocal对象被GC回收时释放了
// 2. set key时,发现对应索引的key为null,则清理value.且会使用启发式扫描,将索引位置附近为null的也清除掉
// 3. 由于我们平常使用ThreadLocal都用static修饰,是一个强引用,所以GC不会回收ThreadLocal对象,
// 使用完后,手动调用ThreadLocal的remove方法,找到Entry数组中与之相对应的Entry对象
// 取消Entry对象对ThreadLocal对象的弱引用,释放掉Entry对象中的value,释放Entry对象
ThreadLocal本身并不存储值,它只是作为key来让线程从ThreadLocalMap获取value
ThreadLocal的set方法,以ThreadLocal自己作为key,资源为value,放到所属线程的ThreadLocalMap中
// java.lang.ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程维护的ThreadLocalMap变量
if (map != null) { map.set(this, value); }// 以ThreadLocal对象自己为key,放入当前线程维护的ThreadLocalMap变量中
else { createMap(t, value); }
}
// java.lang.ThreadLocal#getMap
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // 返回Thread维护的ThreadLocalMap变量
ConcurrentHashMap
HashMap
的键和值可以是null,线程不安全
Hashtable
和ConcurrentHashMap
的键和值都不能为null,二者都是线程安全的
Hashtable并发度低,整个Hashtable对应一把锁,同一时刻只能有一个线程操作它
// 初始容量11,扩容因子是3/4,每次扩容是上次*2+1,计算hash值不需要二次hash
// jdk7
ConcurrentHashMap使用了Segment数组+HashEntry数组+链表的结构,每个Segment对应一把锁,多个线程访问不同的Segment,则不会冲突
// Segment数组不能扩容,由一开始的并发度解决
// 默认Segment的个数是16,你也可以认为ConcurrentHashMap默认支持最多16个线程并发.
// 存储数据的数组可以扩容,扩容因子3/4,超过3/4每次容量翻倍,每个Segment小格子下的数组各扩个的,最低初始容量为2,正常capacity/clevel
// jdk8
ConcurrentHashMap将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突
// 数组+链表\红黑树,初始容量16
// 第一次put元素时,才会创建数组结构(懒汉式)
// 尾插
// 只要满3/4,就会扩容,且重新计算hash
数组的初始长度是>(capacity/扩容因子)的最小的2的次方