ThreadLocal
线程隔离机制。
ThreadLocal实际是一种线程隔离机制,也是为了保证在多线程环境下对于共享变量的访问安全性。
static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0; //初始化一个值
}
};
public static void main(String[] args) {
Thread[] thread = new Thread[5];
for (int i = 0; i < 5; i++) {
thread[i] = new Thread(() -> {
int num = local.get(); //获得的值都是0
local.set(num += 5); //设置到local中
System.out.println(Thread.currentThread().getName()+"-"+num);
});
}
for (int i = 0; i < 5; i++) {
thread[i].start();
}
}
ThreadLocal原理分析
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);
}
第一次当map位空的时候会创建一个ThreadLocalMap。ThreadLocalMap是一个懒加载的方式。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
当map不为空的时候的执行逻辑:
- 根据key的散列哈希计算Entry的数组下标
- 通过线性探索探测从i开始往后一直遍历到数组的最后一个Entry
- 如果map中的key和传入的key相同,表示该数据已经存在,直接覆盖
- 如果map中的key为空,则用新的key、value覆盖,并清理key=null的数据
- rehash扩容
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//根据哈希码和数组长度求元素放置的位置,即数组下标 这个的初始值为0
int i = key.threadLocalHashCode & (len-1); // & 后面有这个标志的讲解
//从i开始往后一直遍历到数组最后一个Entry(线性探索)
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key相等,覆盖value
if (k == key) {
e.value = value;
return;
}
//如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据(弱引用)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//如果超过阈值,就需要扩容了
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
线性探测,是用来解决hash冲突的一种策略。它是一种开发寻址的策略。
例如hash表,它是根据key进行直接访问的数据结构,也就是说我们可以通过hash函数把key映射到hash表中的一个位置来访问记录,从而加快查询的速度。存放记录的数据就是hash表(散列表)
当我们针对一个key通过hash函数计算产生的一个位置,在hash表中已经被另一个键值对占用时,那么线性探测就可以解决这个冲突,这里分为两种情况。
- 写入:查找hash表中离冲突单位最近的空闲单元,把新的键值插入到这个空闲单元
- 查找:根据hash函数计算的一个位置开始往后查找,找到与key对应的value或者找到空的单元。
replaceStaleEntry
分析清理的过程和替换的过程。
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//向前扫描,查找最前面一个无效的slot
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
//通过循环遍历,可以定位到最前面一个无效的slot
if (e.get() == null)
slotToExpunge = i;
//从i开始往后一直遍历到数组最后一个Entry(线性探索)
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//找到匹配后的key以后
if (k == key) {
//更新对应slot的value值
e.value = value;
//与无效的slot进行交换
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 如果最早的一个无效的slot和当前的staleSlot相等,则从i作为清理的起点
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//从slotToExpunge开始做一次连续的清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果当前的slot已经失效,并且向前扫描过程中没有无效slot,则更新slotToexpunge为当前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果key对应在entry中不存在,则直接放在一个新的entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 如果有任何一个无效的slot,则做一次清理
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
0x61c88647 斐波那切数列
private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
magicHash(16);
magicHash(32);
}
private static void magicHash(int size){
int hashCode=0;
for(int i=0;i<size;i++){
hashCode=i*HASH_INCREMENT+HASH_INCREMENT;
System.out.print((hashCode&(size-1))+" ");
}
System.out.println("");
}
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26
1 8 15 22 29 4 11 18 25 0
线性探测
用来解决hash冲突的一种策略
- 写入,找到发生冲突最近的空闲单元
- 查找,从发生冲突的位置,往后查找
ThreadLocal使用弱引用的好处
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- 弱引用的定义是:如果一个对象仅被一个弱引用指向,那么当下一次GC到来时,这个对象一定会被垃圾回收器回收掉;
- 通过源码分析,我们可以看到ThreadLocalMap里面的Entry节点的key值是弱引用类型,当设置key=null的时候,当ThreadLocal使用get、set方法的时候都会去清理这些key为空的数据;
- 如果key为强引用的话,如果当前的ThreadLocalDemo.threadLocal = null,去掉了threadLocal的引用,但此时还存在thread->threadLocalMap->entry->key(threadLocal)的引用,
这样的话除非threadLocal使用结束,不然的话是无法释放掉entry的值的,会造成内存泄漏的情况;
- 如果使用threadLocal线程池的时候,会导致entry内的value不能释放,也会导致内存的泄漏。
ps:
&是什么意思?
&在 java 中做与运算,& 是所有的2进制位数“与”出的最终结果,“与”的规则是两者都为1时才得1,否则就得0
例如
132&15 = ?
答案:4
why?
阿拉伯数字(十进制):132 二进制:10000100
阿拉伯数字(十进制):15 二进制:0000 1111(计算器转换应该是1111,因为两个二进制进行运算时,需要在位数少的前面补零-补码操作)
10000100 & 0000 1111 = 0100 //4
(0100 & 1111 = 0100 )真正的运算是这样的,根据&的规则则取末尾是0的0100
结论:
①、当两个数末尾是1和0时,根据规则取0的数
②、当两个数末尾都是1时,根据规则取数小的,例如1111和0101就是0101