ThreadLocal
每个线程向ThreadLocal设置值,再取值,实现线程之间的隔离
public class ThreadLocalCase1 {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Random random = new Random(); for (int i = 0; i < 5; i++) {Thread thread = new Thread(() -> {int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "开始执行,放入值,值为 : " + value);System.out.println(Thread.currentThread().getName() + "结束执行,进行取值,值为 : " + threadLocal.get());}); thread.setName("thread-" + i); thread.start();}}
}
结果
thread-0开始执行,放入值,值为 : 7406
thread-4开始执行,放入值,值为 : 5258
thread-3开始执行,放入值,值为 : 9672
thread-2开始执行,放入值,值为 : 8583
thread-1开始执行,放入值,值为 : 9311
thread-2结束执行,进行取值,值为 : 8583
thread-3结束执行,进行取值,值为 : 9672
thread-4结束执行,进行取值,值为 : 5258
thread-0结束执行,进行取值,值为 : 7406
thread-1结束执行,进行取值,值为 : 9311
实现了线程之间的隔离性
主线程向ThreadLocal设置值,每个子线程再取值
public class ThreadLocalCase2 {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});thread.setName("thread-1");thread.start();System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}
}
结果
main放入值,值为 : 3831
main进行取值,值为 : 3831
thread-1进行取值,值为 : null
发现主线程向ThreadLocal
设置值,每个子线程再取值时为null,这时需要换用InheritableThreadLocal
InheritableThreadLocal
主线程向InheritableThreadLocal设置值,子线程再取值
public class ThreadLocalCase2 {private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);Thread thread = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});thread.setName("thread-1");thread.start();System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}
}
结果
main放入值,值为 : 2046
main进行取值,值为 : 2046
thread-1进行取值,值为 : 2046
public class ThreadLocalCase3 {private static ExecutorService executor = Executors.newFixedThreadPool(2);private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {for (int i = 0; i < 5; i++) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);executor.execute(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());});System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());threadLocal.remove();}}
}
结果
main放入值,值为 : 8525
main进行取值,值为 : 8525
main放入值,值为 : 7802
main进行取值,值为 : 7802
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 3570
pool-1-thread-2进行取值,值为 : 7802
main进行取值,值为 : 3570
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 5081
main进行取值,值为 : 5081
pool-1-thread-2进行取值,值为 : 7802
main放入值,值为 : 4829
main进行取值,值为 : 4829
pool-1-thread-1进行取值,值为 : 8525
结果发现线程池取出了两次7802和三次8525,主线程中设置的3570、5081、4829在线程池中没有被取出,发生了错误。
这是由于InheritableThreadLocal会保证子线程能读取父线程中的数据,但线程池中的核心线程是复用的,所以有可能会发生重复读取的情况。
TransmittableThreadLocal
开启循环,每个循环中主线程使用InheritableThreadLocal进行设置,使用线程池来进行取值,解决线程复用产生的问题
public class ThreadLocalCase4 {private static ExecutorService executor = Executors.newFixedThreadPool(2);private static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();public static void main(String[] args) {for (int i = 0; i < 5; i++) {Random random = new Random();int value = random.nextInt(10000);threadLocal.set(value);System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);executor.execute(TtlRunnable.get(() -> {System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());}));System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());threadLocal.remove();}}
}
结果
main放入值,值为 : 974
main进行取值,值为 : 974
main放入值,值为 : 4545
main进行取值,值为 : 4545
main放入值,值为 : 5901
pool-1-thread-1进行取值,值为 : 974
main进行取值,值为 : 5901
pool-1-thread-1进行取值,值为 : 5901
pool-1-thread-2进行取值,值为 : 4545
main放入值,值为 : 3716
main进行取值,值为 : 3716
pool-1-thread-1进行取值,值为 : 3716
main放入值,值为 : 2452
main进行取值,值为 : 2452
pool-1-thread-2进行取值,值为 : 2452
结果是即使线程池的线程被复用,读取的结果也是正常的
ThreadLocal原理
- Thread
public class Thread implements Runnable {//省略..../* * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal*/ThreadLocal.ThreadLocalMap threadLocals = null;//省略....
}
- ThreadLocal类:
ThreadLocal
是一个泛型类,用于存储每个线程的本地变量副本。
每个线程通过ThreadLocal
实例获取和操作自己的变量副本,避免了多线程间的资源竞争。
- ThreadLocalMap:
ThreadLocalMap
是ThreadLocal
的内部静态类,用于存储线程局部变量。
每个线程都有一个独立的ThreadLocalMap
实例,用来存储该线程的ThreadLocal
变量。
ThreadLocalMap
以ThreadLocal
实例作为键,实际存储的值作为值。
- get()和set()方法:
- 使用
ThreadLocal
的get()方法,可以获取当前线程的ThreadLocal
变量。
使用ThreadLocal
的set()方法,可以设置当前线程的ThreadLocal
变量。
get()和set()方法内部会调用Thread.currentThread()
获取当前线程,然后在该线程的ThreadLocalMap
中查找或设置对应的值。
内存泄露问题
- 使用
- ThreadLocalMap中的键使用弱引用:
- ThreadLocalMap中的键是对ThreadLocal实例的弱引用。当没有强引用指向ThreadLocal实例时,垃圾回收器会回收这个ThreadLocal实例,导致ThreadLocalMap中的键变为null。
- jdk1.8环境下的ThreadLocal采取嗅探机制,将调用get或set方法时,会主动探测是否含有key为空的value没有被回收的情况,如果有会主动清理。但我们依旧要在使用完后主动的调用remove
InheritableThreadLocal原理
InheritableThreadLocal重写的方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}
Thread结构
public class Thread implements Runnable {//省略..../* * 存储本线程自身的ThreadLocal*/ThreadLocal.ThreadLocalMap threadLocals = null;/** 从父线程集成而来的ThreadLocalMap,*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//省略....
}
主线程中调用InheritableThreadLocal的set方法
set依旧是ThreadLocal
中
public void set(T value) {Thread t = Thread.currentThread();//被InheritableThreadLocal重写,第一次为空ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {//被InheritableThreadLocal重写,创建ThreadLocalMap赋值给inheritableThreadLocals变量createMap(t, value);}
}
这时就是Thread中的inheritableThreadLocals变量存储ThreadLocalMap
子线程初始化
public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {//省略....//inheritThreadLocals为true//parent.inheritableThreadLocals就是在主线程进行set的时候生成为所以不为nullif (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//省略.... }
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
就是将主线程的ThreadLocalMap
拷贝到子线程中
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}
/*** 将父线程的ThreadLocalMap拷贝到此线程中*/
private ThreadLocalMap(ThreadLocalMap parentMap) {//父线程的ThreadLocalMap的entry数组 Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);// 这里的table就是此线程中的ThreadLocalMap的entry数组 table = new Entry[len];// 循环进行拷贝 parentMap 的记录for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {//这里被InheritableThreadLocal重写,直接返回value,不做任何操作Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}
}
到这里就将父线程中的值复制到子线程中了