ThreadLocal和ConcurrentHashMap

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,线程不安全
HashtableConcurrentHashMap的键和值都不能为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

ThreadLocal和ConcurrentHashMap_第1张图片
ThreadLocal和ConcurrentHashMap_第2张图片

// jdk8
ConcurrentHashMap将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突
// 数组+链表\红黑树,初始容量16
// 第一次put元素时,才会创建数组结构(懒汉式)
// 尾插
// 只要满3/4,就会扩容,且重新计算hash
数组的初始长度是>(capacity/扩容因子)的最小的2的次方

ConcurrentHashMap

你可能感兴趣的:(JUC并发,jvm,java)