JAVA篇:Java 多线程 (五)ThreadLocal详解

5 ThreadLocal详解

关键字:ThreadLocal、InheritableThreadLocal、ThreadLocal和局部变量

5.1 ThreadLocal

ThreadLocal是一个泛型类,java.lang.ThreadLocal<T>

这个类提供线程局部变量。可以将ThreadLocal定义为共享变量(全局变量或者static静态变量),每个线程在访问ThreadLocal变量(调用set/get)都会初始化属于线程的变量副本,可以用于存储一些与线程相关的状态、用户id、事务id等数据。

可以通过子类重写initialValue()来设置初始值--默认初始值是null。

5.1.1 ThreadLocal应用

为线程设置递增的线程id,存在于ThreadLocal变量threadids中。

    /* 测试ThreadLocal */public void test1(){
        // 原子整数,线程安全
        AtomicInteger nextInt = new AtomicInteger();
        // 设定threadids的初始值递增
        ThreadLocal<Integer> threadids = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return nextInt.incrementAndGet();//返回递增整数
            }
        };
​
        /* 创建5个子线程并输出threadid的值 */
​
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+": "+threadids.get());//在这个时候调用初始化并返回threadid
​
            }
        };
        int threadNum = 5;
        Thread[] threads = new Thread[threadNum];
​
        for(int i=0;i<threadNum;i++){
            threads[i] = new Thread(runnable);
​
        }
        /*倒序启动子线程*/
        for(int i=threadNum-1;i>=0;i--){
            threads[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        System.out.println(Thread.currentThread().getName()+": "+threadids.get());//在这个时候调用初始化并返回threadid
​
    }

根据代码结果可以看出来threadids中对某个线程初始化其对应值的时机是该线程调用threadids.get或者threadids.set的时候,所以有如下结果:

Thread-4: 1
Thread-3: 2
Thread-2: 3
Thread-1: 4
Thread-0: 5
main: 6

5.1.2 ThreadLocal.withInitial

Java8中ThreadLocal对象提供了一个Lambda构造方式,实现了非常简洁的构造方法:withInitial

这个方法采用Lambda方式传入实现了 Supplier 函数接口的参数。

        // 初始化1
        ThreadLocal<Integer> threadids1 = new ThreadLocal<>();
        System.out.println("使用new初始化的值:"+threadids1.get());
        //初始化2
        ThreadLocal<Integer> threadids2 = ThreadLocal.withInitial(()->100);
        System.out.println("使用withInitial初始化的值:"+threadids2.get());
        //初始化3
        ThreadLocal<HashMap<Integer,Integer>> threadids3 = ThreadLocal.withInitial(HashMap::new);
        System.out.println("使用withInitial初始化Map的值:"+threadids3.get());
使用new初始化的值:null
使用withInitial初始化的值:100
使用withInitial初始化Map的值:{}

5.2 InheritableThreadLocal

ThreadLocal在父线程、子线程之间是完全独立不继承的。

而InheritableThreadLocal给了子线程初始化继承父线程ThreadLocal变量值,以及父线程操作子线程初始化值的方法。但仅仅是初始化值的继承而已,InheritableThreadLocal对于线程依旧是线程独享的,子线程更改值并不影响父线程。

子线程InheritableThreadLocal变量初始化的值默认继承子线程运行时父线程的值,但是也可以通过重写childValue方法来指定子线程初始值和父线程的值的关系。

    /* 测试InheritableThreadLocal */
    public void test3(){
        /* ThreadLocal */
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        /* 默认继承父线程值的InheritableThreadLocal */
        InheritableThreadLocal<Integer> integerInheritableThreadLocal = new InheritableThreadLocal<>();
        /* 设置指定子线程值的InheritableThreadLocal */
        InheritableThreadLocal<Integer> integerInheritableThreadLocal2 = new InheritableThreadLocal<Integer>(){
            @Override
            protected Integer childValue(Integer value){
                return value+10;
            }
        };
​
                /* 主线程设置两个值都为1 */
        threadLocal.set(1);
        integerInheritableThreadLocal.set(1);
        integerInheritableThreadLocal2.set(1);
​
        /* 子线程中获取并更改值 */
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":threadLocal= "+threadLocal.get());
                System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
                System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal2="+integerInheritableThreadLocal2.get());
                integerInheritableThreadLocal.set(2);
                integerInheritableThreadLocal2.set(2);
                System.out.println(Thread.currentThread().getName()+":设置 integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
                System.out.println(Thread.currentThread().getName()+":设置 integerInheritableThreadLocal2="+integerInheritableThreadLocal2.get());
​
            }
        };
​
        /* 创建子线程1 */
        Thread t1 = new Thread(runnable);
        t1.start();
        
​
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        /* 子线程的数据更改不会影响主线程 */
        System.out.println(Thread.currentThread().getName()+":threadLocal= "+threadLocal.get());
        System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal="+integerInheritableThreadLocal.get());
        System.out.println(Thread.currentThread().getName()+":integerInheritableThreadLocal2="+integerInheritableThreadLocal.get());
    }
​

Thread-0:threadLocal= null
Thread-0:integerInheritableThreadLocal=1
Thread-0:integerInheritableThreadLocal2=11
Thread-0:设置 integerInheritableThreadLocal=2
Thread-0:设置 integerInheritableThreadLocal2=2
main:threadLocal= 1
main:integerInheritableThreadLocal=1
main:integerInheritableThreadLocal2=1

5.3 ThreadLocal和局部变量

变量线程独立,其实很容易想到局部变量。在一开始了解到ThreadLocal的定义时,我很难理解,因为我怎么想,这个定义都跟局部变量很像,我无法理解ThreadLocal设计出来的意义。

按照某个说法,可以将局部变量看做是把钱放在自己家里,ThreadLocal变量则是把钱放在银行,虽然每个人各自的账号及钱也只能自己访问,但是钱放在一个同一的地方方便管理。这个区别不是共享变量和局部变量这样子访问权限上的区别,更大的区别在于一种设计上、代码上更加简单明了。

5.X 参考

Java中的ThreadLocal详解

Java多线程9:ThreadLocal源码剖析

Java多线程10:ThreadLocal的作用及使用

Java ThreadLocal变量 - 什么时候用以及如何使用?

ThreadLocal.withInitial

 

0、JAVA多线程编程

Java多线程编程所涉及的知识点包含线程创建、线程同步、线程间通信、线程死锁、线程控制(挂起、停止和恢复)。之前 JAVA篇:Java的线程仅仅了解了部分线程创建和同步相关的小部分知识点,但是其实在编程过程中遇到的事情并不仅仅限于此,所以进行整理,列表如下:

当你深入了解,你就会发现世界如此广袤,而你对世界的了解则是如此浅薄,请永远保持谦卑的态度。
原文地址:https://www.cnblogs.com/liwxmyself/p/15420822.html