一.ThreadLocal的使用
1.1说明
a.ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。
b.ThreadLocal本身不存放数据,而是使用线程中的threadLocals属性来存储每个线程的变量副本。
c.使用线程池时,需要及时清理线程中的ThreadLocal,避免造成污染
d.ThreadLocal在创建子线程时不会把线程本地变量拷贝到子线程中,这就会导致子线程无法获取到本地线程保存的线程信息
1.2声明方式
a.ThreadLocal<Integer> NUM = new ThreadLocal<>();
线程首次使用前,需要先进行调用set方法进行复制,get方法默认返回为null
b.ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0)
实现类为SuppliedThreadLocal,声明时进行处理化
1.3简单使用
static ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0); public static void test1(){ new Thread(() -> { NUM.set(1); System.out.println("threadName:"+Thread.currentThread().getName()+" NUM:"+NUM.get()); }).start(); new Thread(() -> { NUM.set(1); System.out.println("threadName:"+Thread.currentThread().getName()+" NUM:"+NUM.get()); }).start(); }
每个线程都拥有自己的变量副本,线程之间的变量副本互不影响,从而避免了多线程操作共享资源造成的数据不一致问题。
1.4线程池使用
static ThreadLocal<Integer> NUM = ThreadLocal.withInitial(()->0);
public static void test2(){
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,2,10L,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3000));
for(int i = 0 ; i < 6; i++){
poolExecutor.execute(() -> {
if (NUM.get() == null){
NUM.set(0);
}
NUM.set(NUM.get()+1);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
//移除
NUM.remove();
});
}
poolExecutor.shutdown();
}
1.5类关系
线程(Thread)的threadLocals属性来存储每个线程的变量副本。threadLocals属性对应的是一个ThreadLocalMap的静态内部类对象,该对象内部维护了一个Entry数组来存储键值对(其中键是ThreadLocal的引用,值是线程变量的副本)。
Entry使用弱引用指向ThreadLocal的值,预防释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了,从而导致内存泄露的风险。
二.InheritableThreadLocal的使用
2.1说明
a.InheritableThreadLocal可以方便地让子线程自动获取父线程InheritableThreadLocal的数据。
b. 不能使用withInitial方法进行声明初始化
c.使用线程池时,线程不会每次都创建,也就不会进行父子线程之间的数据传递;如果使用池化的线程,只会在首次进行数据传递。
2.2简单使用
static InheritableThreadLocal<Integer> NUM = new InheritableThreadLocal<>();
public static void test1(){
Thread thread1 = new Thread(()->{
NUM.set(NUM.get()+1);
System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
Thread thread3 = new Thread(()->{
NUM.set(NUM.get()+1);
System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
//移除
NUM.remove();
},"thread3");
thread3.start();
//移除
NUM.remove();
},"thread1");
thread1.start();
Thread thread2 = new Thread(()->{
NUM.set(NUM.get()+1);
System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
//移除
NUM.remove();
},"thread2");
thread2.start();
}
public static void main(String[] args) {
NUM.set(1);
test1();
}
InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。
2.3线程池使用
public static void test2(){
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,2,10L,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(3000));
for(int i = 0 ; i < 10 ; i++){
poolExecutor.execute(() -> {
NUM.set(NUM.get()+1);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+";NUM:"+NUM.get());
//移除
NUM.remove();
});
}
poolExecutor.shutdown();
}
只有线程首次使用时会继承父线程的值,线程移除InheritableThreadLocal时,后续使用无法继续使用父线程的值
2.4实现说明
2.4.1InheritableThreadLocal重写
InheritableThreadLocal重写了getMap和createMap方法,关联Thread中的ThreadLocal.ThreadLocalMap inheritableThreadLocals属性
2.4.2线程初始化
线程在初始化时会判断父线程的inheritableThreadLocals属性是否为空,不为空时会继承父线程的inheritableThreadLocals属性