为何引入ThreadLocal
ThreadLocal
对象可以提供线程局部变量,每个线程Thread
拥有一份自己的副本变量,多个线程互不干扰. 下面举例说明引入ThreadLocal的有优点.
SimpleDateFormat
private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void test(){
String dateStr = f.format(new Date());
// 业务流程
}
SimpleDateFormat存在线程安全问题. 测试如下:
public class Main {
private static SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
while (true) {
new Thread(() -> {
String dateStr = f.format(new Date());
System.out.println(new Date());
System.out.println(dateStr);
try {
Date parseDate = f.parse(dateStr);
String dateStrCheck = f.format(parseDate);
boolean equals = dateStr.equals(dateStrCheck);
if (!equals) {
System.out.println(equals + " " + dateStr + " " + dateStrCheck);
} else {
System.out.println(equals);
}
} catch (ParseException e) {
System.out.println(e.getMessage());
}
}).start();
}
}
}
可以发现, 不仅存在格式化不一致的问题, 还存在输入错误问题. 因为SimpleDateFormat有个成员变量calendar
, 且会被修改.
要想解决SimpleDateFormat的问题, 可以每次使用时采用new重新创建对象, 但对于同一线程使用是安全的, 没必要每次新建. 引入ThreadLocal可解决该问题, 实测下面的例子都是equal的
public class Main {
private static ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
while (true) {
new Thread(() -> {
SimpleDateFormat f = threadLocal.get();
String dateStr = f.format(new Date());
System.out.println(new Date());
System.out.println(dateStr);
try {
Date parseDate = f.parse(dateStr);
String dateStrCheck = f.format(parseDate);
boolean equals = dateStr.equals(dateStrCheck);
if (!equals) {
System.out.println(equals + " " + dateStr + " " + dateStrCheck);
} else {
System.out.println(equals);
}
} catch (ParseException e) {
System.out.println(e.getMessage());
}
}).start();
}
}
}
源码分析
数据结构ThreadLocalMap
ThreadLocal内部使用了ThreadLocalMap, 底层采用数组实现, 既然是Map, 就需要计算哈希以及解决哈希冲突. 其中key为thread
哈希算法
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
static class ThreadLocalMap {
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
其中i就是当前key在hash表数组中的下标位置, 每次新建一个ThreadLocal对象, nextHashCode会增加0x61c88647, 这个值属于斐波那契数 也叫 黄金分割数, 用于均匀散列数字.
public static void main(String[] args) {
int hashInc = 0x61c88647;
for (int i = 0; i < 16; i++) {
int hashCode = i * hashInc;
int idx = hashCode & 15;
System.out.println("斐波那契散列:" + idx + " 普通散列:" + (String.valueOf(i).hashCode() & 15));
}
}
斐波那契散列:0 普通散列:0
斐波那契散列:7 普通散列:1
斐波那契散列:14 普通散列:2
斐波那契散列:5 普通散列:3
斐波那契散列:12 普通散列:4
斐波那契散列:3 普通散列:5
斐波那契散列:10 普通散列:6
斐波那契散列:1 普通散列:7
斐波那契散列:8 普通散列:8
斐波那契散列:15 普通散列:9
斐波那契散列:6 普通散列:15
斐波那契散列:13 普通散列:0
斐波那契散列:4 普通散列:1
斐波那契散列:11 普通散列:2
斐波那契散列:2 普通散列:3
斐波那契散列:9 普通散列:4
设置值
new ThreadLocal<>().set("aa");
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- 若k等于key: 说明找到了对应的ThreadLocal, 直接更新
- 若key为null: 说明这个位置的
ThreadLocal
已经被垃圾回收,调用replaceStaleEntry
方法替换这个过期的条目 - 若没有对应的ThreadLocal: 新建item, 尝试清理过期元素, 若空间还是不够则扩容.
标签:契散列,斐波,ThreadLocal,println,new,散列 From: https://www.cnblogs.com/shmilyt/p/18487801