应用场景
Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复,org.springframework.transaction.support.TransactionSynchronizationManager
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
原理解析
成员变量
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
....
}
set方法
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
为什么entry数组的大小,以及初始容量都必须是2的幂?
对于 firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 以及很多源码里面都是使用 hashCode &(2^n -1) 来代替hashCode%2^n
使用位运算替代取模,提升计算效率。
为了使不同 hash 值发生碰撞的概率更小,尽可能促使元素在哈希表中均匀地散列。
为什么需要数组呢?没有了链表怎么解决Hash冲突呢
一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里,所以肯定要数组来存
HaseMap解决hash冲突是由链表和红黑树(jdk1.8)来解决的,ThreadLocalMap采用「线性探测」的方式解决hash冲突,
在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1),
如果当前位置是空的,就初始化一个Entry对象放在位置i上,
如果位置i不为空,如果这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value,
如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止
ThreadLocal的实例以及其值存放在栈上
栈内存可以理解成线程的私有内存
ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值其实也是被线程实例持有,它们都是位于堆上
内存泄漏
ThreadLoacal<Object> key = new ThreadLoacal()<> -- 强引用
Object value = new Object();
key.set(value) -- 弱引用
ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露,比如线程池复用
解决方式:代码最后使用remove
为什么ThreadLocalMap的key要设计成弱引用
key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景
SimpleDateFormat
SimpleDateFormat 其实是有状态的,它使用一个 Calendar 成员变量来保存状态
UnSafeDateUtil
public class UnSafeDateUtil {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String format(Date date) {
return dateFormat.format(date);
}
public static Date parse(String dateStr) throws ParseException {
return dateFormat.parse(dateStr);
}
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(1);
final String[] strs = new String[] {"2016-01-01 10:24:00", "2016-01-02 20:48:00", "2016-01-11 12:24:00"};
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i1 = 0; i1 < 10; i1++){
try {
System.out.println(Thread.currentThread().getName()+ "\t" + parse(strs[i1 % strs.length]));
Thread.sleep(100);
} catch (ParseException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
latch.countDown();
}
}
输出内容:
java.lang.NumberFormatException: For input string: "11011101EE.221101"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
SafeDateUtil
public class SafeDateUtil {
/*private static ThreadLocal<SimpleDateFormat> local = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String format(Date date) {
return local.get().format(date);
}
public static Date parse(String dateStr) throws ParseException {
return local.get().parse(dateStr);
}*/
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>();
//延迟加载
private static SimpleDateFormat getDateFormat() {
SimpleDateFormat dateFormat = local.get();
if (dateFormat == null) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
local.set(dateFormat);
}
return dateFormat;
}
public static String format(Date date) {
return getDateFormat().format(date);
}
public static Date parse(String dateStr) throws ParseException {
return getDateFormat().parse(dateStr);
}
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(1);
final String[] strs = new String[] {"2016-01-01 10:24:00", "2016-01-02 20:48:00", "2016-01-11 12:24:00"};
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i1 = 0; i1 < 10; i1++){
try {
System.out.println(Thread.currentThread().getName()+ "\t" + parse(strs[i1 % strs.length]));
Thread.sleep(100);
} catch (ParseException | InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
latch.countDown();
}
}
InheritableThreadLocal
InheritableThreadLocal提供了一种父子线程之间的数据共享机制,但是线程池的话,线程会复用,所以会存在问题,子线程只有在线程对象创建的时候才会把父线程inheritableThreadLocals中的数据复制到自己的inheritableThreadLocals中
public class InheritableThreadLocalDemo {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set("threadLocal主线程的值");
Thread.sleep(100);
new Thread(() -> System.out.println("子线程获取threadLocal的主线程值:" + threadLocal.get())).start();
Thread.sleep(100);
inheritableThreadLocal.set("inheritableThreadLocal主线程的值");
new Thread(() -> System.out.println("子线程获取inheritableThreadLocal的主线程值:" + inheritableThreadLocal.get())).start();
}
}
输出内容:
子线程获取threadLocal的主线程值:null
子线程获取inheritableThreadLocal的主线程值:inheritableThreadLocal主线程的值
FastThreadLocal
弥补ThreadLocal的内存泄漏的的不足,使用完 FastThreadLocal 之后不用 remove 了,因为在 FastThreadLocalRunnable 中已经加了移除逻辑,在线程运行完时会移除全部绑定在当前线程上的所有变量
FastThreadLocal 在大量频繁读写操作时效率要高于 ThreadLocal
public class FastThreadLocalTest {
public static final int MAX = 100000;
public static void main(String[] args) {
new Thread(FastThreadLocalTest::threadLocal).start();
new Thread(FastThreadLocalTest::fastThreadLocal).start();
}
private static void fastThreadLocal() {
long start = System.currentTimeMillis();
DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);
FastThreadLocal[] fastThreadLocal = new FastThreadLocal[MAX];
for (int i = 0; i < MAX; i++) {
fastThreadLocal[i] = new FastThreadLocal<>();
}
Thread thread = defaultThreadFactory.newThread(() -> {
for (int i = 0; i < MAX; i++) {
fastThreadLocal[i].set("java: " + i);
}
System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));
for (int i = 0; i < MAX; i++) {
for (int j = 0; j < MAX; j++) {
fastThreadLocal[i].get();
}
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start));
}
private static void threadLocal() {
long start = System.currentTimeMillis();
ThreadLocal[] threadLocals = new ThreadLocal[MAX];
for (int i = 0; i < MAX; i++) {
threadLocals[i] = new ThreadLocal<>();
}
Thread thread = new Thread(() -> {
for (int i = 0; i < MAX; i++) {
threadLocals[i].set("java: " + i);
}
System.out.println("threadLocal set: " + (System.currentTimeMillis() - start));
for (int i = 0; i < MAX; i++) {
for (int j = 0; j < MAX; j++) {
threadLocals[i].get();
}
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threadLocal total: " + (System.currentTimeMillis() - start));
}
}
输出
threadLocal set: 85
fastThreadLocal set: 91
fastThreadLocal total: 121
threadLocal total: 16367
在大量读写面前,写操作的效率差不多,但读操作 FastThreadLocal 比 ThreadLocal 快的不是一个数量级,简直是秒杀 ThreadLocal
MAX值调到1000
threadLocal set: 4
threadLocal total: 14
fastThreadLocal set: 23
fastThreadLocal total: 32
区别不是很明显,但是threadLocal明显优势
transmittable-thread-local
解决线程池,池化过程中,父线程到子线程的值传递问题
原理
使用TtlRunnable/Ttlcallable包装了Runnable/Callable类
在TtlRunnable/Ttlcallable初始化时capture TransmittableThreadLocal变量
在run方法调用runnable.run()前进行replay,设置到当前线程ThreadLocal
在run方法调用runnable.run()后进行restore,上下文还原,也就是replay的反向操作