# 并发编程之美（1）并发编程基础二_会写代码的花城的博客-CSDN博客_并发编程之美

By [UniGood](https://paragraph.com/@unigood) · 2022-05-25

---

1…9线程死锁
-------

### 1.9.1什么是线程死锁

死锁是指两个或两个以上的线程在执行过程中，因争夺资源而造成的互相等待的现象，在无外力作用的情况下，这些线程会一直相互等待而无法继续运行下去

![在这里插入图片描述](https://storage.googleapis.com/papyrus_images/a8b9ca5b94eb106488bac2f4fa7d86c83a7d7ac2e07dcc39e6c62d0347ca969f.png)

在这里插入图片描述

线程A 己经持有了资源2 ， 它同时还想申请资源l ， 线程B 已经持有了资源l ，它同时还想申请资源2 ， 所以线程l 和线程2 就因为相互等待对方已经持有的资源，而进入了死锁状态。

为什么会产生死锁呢？用原书（并发编程之美）中的解释

**死锁的产生必须具备以下四个条件。互斥条件：** 指线程对己经获取到的资源进行排它性使用， 即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源，则请求者只能等待，直至占有资源的线程释放该资源。**请求并持有条件：** 指一个线程己经持有了至少一个资源， 但又提出了新的资源请求，而新资源己被其他线程占有，所以当前线程会被阻塞，但阻塞的同时并不释放自己己经获取的资源。**不可剥夺条件：** 指线程获取到的资源在自己使用完之前不能被其他线程抢占， 只有在自己使用完毕后才由自己释放该资源。**环路等待条件**： 指在发生死锁时， 必然存在一个线程→资源的环形链， 即线程集合{T0 , T1 T2 ，…， Tn ｝中的T0 正在等待一个Tl 占用的资源， T1正在等待T2 占用的资源，……Tn 正在等待己被T0 占用的资源。

> 死锁例子

    public class DedLock {
        //创建资源
        private static Object resourceA = new Object();
        private static Object resourceB = new Object();
    
        public static void main(String[] args) {
            new Thread(()->{
                //先给我们的资源A加锁
                synchronized (resourceA){
                    System.out.println(Thread.currentThread()+"获得资源A");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread()+"等待获取资源B");
                    //在对资源B加锁
                    synchronized (resourceB)
                    {
                        System.out.println(Thread.currentThread()+"获得资源B");
                    }
                }
            }).start();
            new Thread(()->{
                //先给我们的资源A加锁
                synchronized (resourceB){
                    System.out.println(Thread.currentThread()+"获得资源B");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread()+"等待获取资源A");
                    //在对资源B加锁
                    synchronized (resourceA)
                    {
                        System.out.println(Thread.currentThread()+"获得资源B");
                    }
                }
            }).start();
        }
    }
    

    结果
    Thread[Thread-0,5,main]获得资源A
    Thread[Thread-1,5,main]获得资源B
    Thread[Thread-1,5,main]等待获取资源A
    Thread[Thread-0,5,main]等待获取资源B
    

这就是典型的死锁例子，我们先锁了资源A，在资源A中又对资源B加锁，但是因为我们线程A睡了1s，期间我们的线程B已经对资源B先加锁了，线程B先对资源B加锁但是又想获取资源A，就导致两者互相争抢，又不释放自己的锁，导致死锁

**他们满足上面我们说的死锁产生具备的条件么？**

1.resourc eA 和re sourc eB 都是互斥资源，当线程A 调synchronized(resource A)方法获取到resourceA 上的监视器锁并释放前， 线程B 再调用synchronized(resourceA） 方法尝试获取该资源会被阻塞，只有线程A 主动释放该锁， 线程B 才能获得， 这满足了资源互斥条件

2.线程A 首先通过synchronized(resourceA） 方法获取到resourceA 上的监视器锁资源，然后通过synchronized(resourceB） 方法等待获取resourceB 上的监视器锁资源， 这就构成了请求并持有条件。

也就是请求B但是我还持有A

3.构成了资源的不可剥夺条件，就是我们线程A只要不是自己主动释放资源A的监视器锁，那么其他线程(线程B)是不会掠夺走的

4.环路等待条件就是，线程A锁了资源A但是请求资源B，但是线程B锁了资源B，又去请求资源A，形成闭合

那么如何避免线程死锁呢？

### 1.9.2如何避免线程死锁

字需要破坏形成死锁的一个必要条件即可。

目前我们只有**请求并持有**和**环路等待条件**是可以被破坏的

造成死锁的原因其实和申请资源的顺序有很大关系， 使用资源申请的有序性原则就可以避免死锁，那么什么是资源申请的有序性呢？我们对上面线程B 的代码进行如下修改

    new Thread(()->{
        //先给我们的资源A加锁
        synchronized (resourceA){
            System.out.println(Thread.currentThread()+"获得资源B");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+"等待获取资源A");
            //在对资源B加锁
            synchronized (resourceB)
            {
                System.out.println(Thread.currentThread()+"获得资源A");
            }
        }
    }).start();
    

结果

    Thread[Thread-0,5,main]获得资源A
    Thread[Thread-0,5,main]等待获取资源B
    Thread[Thread-0,5,main]获得资源B
    Thread[Thread-1,5,main]获得资源B
    Thread[Thread-1,5,main]等待获取资源A
    Thread[Thread-1,5,main]获得资源A
    

也就是说资源B没有被加锁，虽然两个线程都对资源A加锁了，但是某个线程先执行，好后获得B之后就是放了资源A，另一个线程能拿到了

这就是资源的有序分配，资源的有序性破坏了资源的请求并持有条件和环路等待条件， 因此避免了死锁。

1.10守护线程与用户线程
-------------

Java 中的线程分为两类，分别为daemon 线程（守护线程〉和user 线程（用户线程）。

在JVM启动的时候会调用main函数，main函数所在的线程就是一个**用户线程**

当然JVM内部同时还启动了好多**守护线程**，比如**垃圾回收线程**。

关于垃圾回收线程后续会在JVM的笔记中详细提到

> 如何创建守护线程

    public class daemon {
        public static void main(String[] args) {
            Thread thread = new Thread(()->{});
            //设置为守护线程
            thread.setDaemon(true);
            thread.start();
        }
    }
    

> 用户线程与守护线程的区别

**区别之一**是当最后一个非守护线程结束时， NM 会正常退出，而不管当前是否有守护线程，也就是说守护线程是否结束并不影响NM 的退出。言外之意，只要有一个用户线程还没结束， 正常情况下NM 就不会退出。

    public class daemon {
        public static void main(String[] args) {
            Thread thread = new Thread(()->{
                for (;;){}
            });
            //启动子线程
            thread.start();
            System.out.println("主线程完毕");
        }
    }
    结果是
        主线程完毕
    

如上代码在main 线程中创建了一个thread 线程，在thread 线程里面是一个无限循环。从运行代码的结果看， main 线程已经运行结束了，那么JVM进程己经退出了吗？在IDE的输出结果右上侧的红色方块说明，JVM 进程并没有退出。另外，我们亦可以通过jps来查看

这个结果说明了当父线程结束后，子线程还是可以继续存在的，也就是**子线程的生命周期并不受父线程的影响**。这也说明了在用户线程还存在的情况下JVM 进程并不会终止。那么我们把上面的thread 线程设置为守护线程后，再来运行看看会有什么结果：

    public class daemon {
        public static void main(String[] args) {
            Thread thread = new Thread(()->{
                for (;;){}
            });
            //设置为守护线程
            thread.setDaemon(true);
            //启动子线程
            thread.start();
            System.out.println("主线程完毕");
        }
    }
    结果是
        主线程完毕
    
        进程已结束，退出代码 0
    

在启动线程前将线程设置为守护线程，执行后的输出结果显示，JVM进程己经终止了，执行\*\*\*ps -eaf |grep java\*\*\* 也看不到JVM 进程了。在这个例子中， main 函数是唯一的用户线程， thread 线程是守护线程，当main 线程运行结束后， JVM 发现当前己经没有用户线程了，就会终止JVM 进程。

由于这里的守护线程执行的任务是一个死循环，这也说明了如果当前进程中不存在用户线程，但是还存在正在执行任务的守护线程，则JVM不等守护线程运行完毕就会结束JVM进程。

main 线程运行结束后， JVM会自动启动一个叫作DestroyJava VM 的线程， 该线程会等待所有用户线程结束后终止JVM进程。下面通过简单的JVM代码来证明这个结论

    int JNICALL
    JavaMain(void * args)
        ／／执行Java 中的ma 工n函数
        (*env) - >CallStaticVoidMethod(env , mainClass, mainID, mainArgs) ;
        //main 函数返回值
        ret = (*env)->ExceptIonOccurred(env) == NULL ? 0: 1 ;
        ／／等待所有非守护线程结束， 然后销毁♂月4进程
        LEAVE();
    }
    

LEAVE 是C 语言里面的一个宏定义，具体定义如下。

    #define LEAVE () \
        do {\
        if ( (*vm) >DetachCurrentThread(vm) 1= JNI_OK ) 	{ \
            JLI_ReportErrorMessage(JVM_ERROR2) ; \
            ret = l ; \
        }\
        if (JNI_TRUE) { \
            ( *vm)->DestroyJavaVM (vm); \
            return ret; \
        }\
    } while_(JNI FALSE)
    

该宏的作用是创建一个名为DestroyJava VM 的线程，来等待所有用户线程结束

在Tomcat 的NIO 实现NioEndpoint 中会开启一组接受线程来接受用户的连接请求，以及一组处理线程负责具体处理用户请求，那么这些线程是用户线程还是守护线程呢？

下面我们看一下NioEndpoint 的startlntemal 方法。

    public void startInternal() throws Exception{
        if(!running){
            running = true;
            paused = false;
            ...
            //创建处理线程
            pollers = new Poller[get PollerThreadCount () ] ;
            for (int i=O ; i<pollers . length; i++) {
                pollers [i] = new Poller () ;
                Thread pollerThread ＝new Thread (pollers [i],getName () +"- Client Poller-"＋ i ) ;
                pollerThread.setPriority (threadPriority ) ;
                pollerThread.setDaemon(true );
                //声明为守护线程
                pollerThread.start() ;
        }
    }
    protected final void startAcceptorThreads() {
        int count= getAcceptorThre adCount();
        acceptors= new Acceptor[count];
        for (int i = O;i<count; i ++) {
            acceptors [i] = createAcceptor () ;
            String threadName = getName ()+"- Acceptor-" +i;
            acceptors[i].setThreadName(threadName) ;
            Thread t =new Thread(acceptors[i],threadName) ;
            t.setPriority(getAcceptorThreadPriority()) ;
            t.setDaemon(getDaemon());//设置是否为守护线程，默认为守护线程
            t . start() ;
        }
    }
        private boolean daemon = true;
        public void setDaemon(boolean b) { daemon= b ; }
        public boolean getDaemon () { return daemon ; }
    

在如上代码中，在默认情况下， 接受线程和处理线程都是守护线程， 这意味着当tomcat 收到shutdown 命令后并且没有其他用户线程存在的情况下tomcat 进程会马上消亡，而不会等待处理线程处理完当前的请求。

> 总结

如果你希望在主线程结束后JVM 进程马上结束，那么在创建线程时可以将其设置为守护线程，如果你希望在主线程结束后子线程继续工作，等子线程结束后再让JVM进程结束，那么就将子线程设置为用户线程。

1.11 ThreadLocal
----------------

多钱程访问同一个共享变量时特别容易出现并发问题，特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全，一般使用者在访问共享变量时需要进行适当的同步，如下图所示

![在这里插入图片描述](https://storage.googleapis.com/papyrus_images/d2724bbed43f3074f2ad4ae5f482a04a40e73c036a77c0b5c9760e53bae0999c.png)

在这里插入图片描述

我们的同步方式一般的都是用加锁的方式来实现同步，那么有没有一种方式可以做到，当创建一个变量后， **每个线程对其进行访问的时候访问的是自己线程的变量呢？**\====>**ThreadLocal**

> 什么是ThreadLocal

ThreadLocal 是JDK 包提供的，它**提供了线程本地变量**，也就是如果你创建了一个ThreadLocal 变量，那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时，实际操作的是自己本地内存里面的变量，从而避免了线程安全问题。创建一个ThreadLocal 变量后，**每个线程都会复制一个变量到自己的本地内存**，如下图所示。

![在这里插入图片描述](https://storage.googleapis.com/papyrus_images/1d5a617151906603ad543f230438c7a98c15310fcf2f954313c08205d7c0104a.png)

在这里插入图片描述

### 1.11.1 ThreadLocal使用示例

    public class ThreadLocalTest {
        static void print(String str){
            //1.1打印当前线程本地内存中localVariable变量的值
            System.out.println(str+"："+localVairable.get());
            //1.2清除当前线程池本地内存中的localVairable变量
            //localVairable.remove();
        }
    
        //2.创建ThreadLocal变量
        static ThreadLocal<String> localVairable = new ThreadLocal<>();
    
        public static void main(String[] args) {
            new Thread(()->{
                //3.1设置线程A中本地变量localVairable的值
                localVairable.set("线程A的localVairable");
                //3.2调用打印函数
                print("线程A");
                //3.3打印本地变量的值
                System.out.println("线程A的localVairable清除后："+localVairable.get());
            }).start();
            new Thread(()->{
                //3.1设置线程A中本地变量localVairable的值
                localVairable.set("线程B的localVairable");
                //3.2调用打印函数
                print("线程B");
                //3.3打印本地变量的值
                System.out.println("线程B的localVairable清除后："+localVairable.get());
            }).start();
        }
    }
    

    结果是：
    
    线程A：线程A的localVairable
    线程B：线程B的localVairable
    线程A的localVairable清除后：线程A的localVairable
    线程B的localVairable清除后：线程B的localVairable
    

我们使用了set设置了localVariable 的值，这其实是设置A线程本地内存的一个副本，这个副本B是访问不了的。

上面的例子是我们没有执行清除本地内存副本的操作，我们放开注释执行，结果变为

    线程A：线程A的localVairable
    线程B：线程B的localVairable
    线程A的localVairable清除后：null
    线程B的localVairable清除后：null
    

### 1.11.2 ThreadLocal的实现原理

相关类图

![image](https://storage.googleapis.com/papyrus_images/6588e2224c1101099759ff318ade3a5090040f73cf09e29f753e1dcb98c4e0a2.png)

image

书中说道

Thread 类中有一个**threadLocals** 和一个**inheritableThreadLocals** ， 它们都是ThreadLocalMap 类型的变量， 而ThreadLocalMap 是一个定制化的Hashmap 。**在默认情况下， 每个线程中的这两个变量都为null** ，源码中就有体现

    protected T initialValue() {
        return null;
    }
    

只有当前线程第一次调用ThreadLocal 的set 或者get 方法时才会创建它们。

其实每个线程的本地变量不是存放在ThreadLocal 实例里面，而是存放在调用线程的threadLocals 变量里面。也就是说， \*\*ThreadLocal 类型的本地变量存放在具体的线程内存空间中。\*\*ThreadLocal 就是一个工具壳，

它通过set 方法把value 值放入调用线程的threadLocals 里面并存放起来， 当调用线程调用它的get 方法时，再从当前线程的threadLocals 变量里面将其拿出来使用。如果调用线程一直不终止， 那么这个本地变量会一直存放在调用线程的threadLocals 变量里面，所以当不需要使用本地变量时可以通过调用ThreadLocal 变量的remove 方法，**从当前线程的threadLocals 里面删除该本地变量。**

另外， Thread 里面的threadLocals 为何被设计为map 结构？很明显是因为每个线程可以关联多个ThreadLocal 变量。

> 1 void set(T value)

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //将当前线程作为key，去查找对应的线程变量
        ThreadLocalMap map = getMap(t);
        //如果存在这个线程对应的变量
        if (map != null)
            map.set(this, value);
        else
            //如果对应的线程变量不存在，就新建，将key，value保存
            createMap(t, value);
    }
    

getMap(Thread t）的源码如下。

    ThreadLocalMap getMap(Thread t) {
        //传进来一个线程，返回这个线程的变量threadLocals
        return t.threadLocals;
    }
    

可以看到， getMap(t）的作用是获取线程自己的变量threadLocal s, threadlocal 变量被绑定到了线程的成员变量上

如果getMap(t）的返回值不为空，则把value 值设置到threadLocals 中，也就是把当前变量值放入当前线程的内存变量threadLocals 中

threadLocals 是一个HashMap 结构， 其中key 就是当前ThreadLocal 的实例对象引用， value 是通过set 方法传递的值。

如果getMap(t）返回空值则说明是第一次调用set 方法，这时创建当前线程的threadLocals 变量。下面来看\*\*createMap(t, value）\*\*做什么。

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    

也就是说当前线程没有threadLocals这个变量时，她就new一个

ThreadLocalMap**线程作为键，设置的内容为值**

> 1.  T get()
>     

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //查找当前线程存不存在对应的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果threadLocals变量不为null，则返回对应本地变量的值
        if (map != null) {
            //
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                //取出我们的值 @SuppressWarnings正压警告注解
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果没有threadLocals这个变量，则初始化一个
        return setInitialValue();
    }
    

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    

    private T setInitialValue() {
        //初始化为null
        T value = initialValue();
        //获得当前线程
        Thread t = Thread.currentThread();
        //查找当前线程存不存在对应的threadLocals变量
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //有的话值设置为null
            map.set(this, value);
        else
            //没有的话创建，
            createMap(t, value);
        return value;
    }
    

如果当前线程的threadLocal s 变量不为空， 则设置当前线程的本地变量值为null ， 否则调用createMap 方法创建当前线程的createMap 变量。

> 3.void remove()

    public void remove() {
        //查询该线程有没有绑定的threadLocals变量
        ThreadLocalMap m = getMap(Thread.currentThread());
        //如果有，移除
        if (m != null)
            m.remove(this);
    }
    

> 总结

​ 在每个线程内部都有一个名为threadLocals 的成员变量， 该变量的类型为Hash Map ， 其中key 为我们定义的ThreadLocal 变量的this 引用， value 则为我们使用set 方法设置的值。**每个线程的本地变量存放在线程自己的内存变量threadLocals 中**，如果当前线程一直不消亡， 那么这些本地变量会一直存在， **所以可能会造成内存溢出**， 因此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应线程的threadLocals 中的本地变量。在高级篇要讲解的只JC 包里面的ThreadLocalRandom ， 就是借鉴ThreadLocal 的思想实现的， 后面会具体讲解。

### 1.11.3 ThreadLocal不支持继承性

首先看一个例子

    public class TestThreadLocal {
        //创建线程变量
        public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        public static void main(String[] args) {
            //2.设置线程变量
            threadLocal.set("你好，world");
            //3.启动子线程
            new Thread(()->{
                System.out.println("子线程："+threadLocal.get());
            }).start();
            //4.输出主线成的变量值
            System.out.println("main："+threadLocal.get());
        }
    }
    

结果是：

    main：你好，world
    子线程：null
    

也就是说，同一个ThreadLocal 变量在父线程中被设置值后， 在子线程中是获取不到的。根据上节的介绍，这应该是正常现象，因为在子线程thread 里面调用get 方法时当前线程为thread 线程，而这里调用s et 方法设置线程变量的是main 线程，两者是不同的线程，自然子线程访问时返回null 。

那如果我们把set放到子线程的函数体里面，结果就会是

    main：null
    子线程：你好，world
    

那么有没有办法让子线程能访问到父线程中的值？ 答案是有。

### 1.11.4 lnheritableThreadLocal 类

InheritableThreadLocal继承自ThreadLocal ， 其提供了一个特性，就是让子线程可以访问在父线程中设置的本地变量。

下面看一下InheritableThreadLocal 的代码。

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
        //1
        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);
        }
    }
    

Inheritab I e ThreadLocal 继承了ThreadLocal ，并重写了三个方法

InheritableThreadLocal 重写了**createMap 方法**， 那么现在当第一次调用set 方法时，创建的是当前线程的inheritableThreadLocals 变量的实例而不再是threadLocals 。

当调用get 方法获取当前线程内部的map 变量时， 获取的是inheritableThreadLocals 而不再是threadLocals 。

也就是说我们用了lnheritableThreadLocal 类的话，threadLocals 变成了inheritableThreadLocals

> childValue

下面我们看一下重写的代码（ 1 ）何时执行， 以及如何**让子线程可以访问父线程的本地变量**。这要从创建Thread 的代码说起，打开Thread 类的默认构造函数，代码如下。

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    

    private void init (ThreadGroup g , Runnable target , String name，long stacksize , AccessControlContext ace) {
    //(4 ）获取当前线程  这里获取的是我们的main线程
    Thread parent = currentThread( );
    //(5 ）如采父线程的inheritableThreadLocals变量不为null
    if (parent.inheritableThreadLocals != null )
        //(6 ）设置子线程中的inheritableThreadLocals变量
        this.inheritableThreadLocals =
    ThreadLocal.createinheritedMap(parent.inheritableThreadLocals);
    this .stackSize = stackSize;
    tid = nextThreadID() ;
    }
    

代码在创建线程时，在构造函数里面会调用in it 方法。代码（ 4 ）获取了当前线程（**这里是指main 函数所在的线程**，也就是父线程〉。

然后代码（ 5 ）判断main 函数所在线程里面的inheritableThreadLocals 属性是否为null 。

前面我们讲了InheritableThreadLocal 类的get 和set 方法操作的是inheritableThreadLocals ，所以这里的inheritableThreadLocal 变量不为null ，因此会执行代码（ 6 ）。下面看一下createlnh eritedMap 的代码

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
    

可以看到，在createlnheri tedMap 内部使用父线程的inheritableThreadLocals 变量作为构造函数创建了一个新的ThreadLocalMap 变量， 然后赋值给了子线程的inheritableThreadLocals 变量

下面我们看看在ThreadLocalMap 的构造函数内部都做了什么事情

    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
    
        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) {
                    //调用重写的方法
                    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++;
                }
            }
        }
    }
    

在该构造函数内部把父线程的inheritabl eThreadLoca l s 成员变量的值复制到新的ThreadLoca!Map 对象中，其中l代码（ 7 ）调用了Inheri tab leThreadLocal 类重写的代码（ 1 ） 。

> 总结

InheritableThreadLocal 类通过重写代码。〉和（ 3 ） 让本地变量保存到了具体线程的inheritableThreadLocal s 变量里面，那么线程在通过InheritableThreadLocal 类实例的set 或者get 方法设置变量时，就会创建当前线程的inheritableThreadLocals 变量。当父线程创建子线程时，构造函数会把父线程inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals 变量里面。

把1.11.3 节中的代码（ 1 ）修改为

    public class TestThreadLocal {
        //创建线程变量
        public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    
        public static void main(String[] args) {
            //2.设置线程变量
            threadLocal.set("你好，world");
            //3.启动子线程
            new Thread(()->{
    
                System.out.println("子线程："+threadLocal.get());
            }).start();
            //4.输出主线成的变量值
            System.out.println("main："+threadLocal.get());
        }
    }
    

    结果为
        main：你好，world
        子线程：你好，world
    

那么在什么情况下需要子线程可以获取父线程的threadlocal 变量呢？

情况还是蛮多的，比如子线程需要使用存放在threadlocal 变量中的用户登录信息，再比如一些中间件需要把统一的id 追踪的整个调用链路记录下来。

其实子线程使用父线程中的threadlocal 方法有多种方式， 比如创建线程时传入父线程中的变量，并将其复制到子线程中，或者在父线程中构造一个map 作为参数传递给子线程，但是这些都改变了我们的使用习惯，所以在这些情况下InheritabI e ThreadLocal 就显得比较有用。

---

*Originally published on [UniGood](https://paragraph.com/@unigood/1-csdn)*
