# Java拾遗-并发-ThreadLocal

一个个小例子深入了解知识点

# 11. ThreadLocal

ThreadLocal 的几个例子

# 11.1. 不使用ThreadLocal出现问题

/**
 * ThreadLocal - 线程副本 - 线程隔离特性 - 未使用ThreadLocal
 * https://www.jianshu.com/p/6fc3bba12f38
 *
 * 下面代码没有使用ThreadLocal,两个线程读取的都是同一个Person类,线程安全问题
 * Person类volatile加不加都一样,因为是引用类型,除非Person类引用变化了,volatile对于属性是不生效的
 * 可是存在某些需求,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象
 *
 * 当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal
 *
 * 常见的就是数据库连接、Session管理等
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/4/22 17:34
 */
public class T11_ThreadLocal_1 {

    public static class Person {
        public String name = "WangMing";
    }

    public /*volatile*/ static Person person = new Person();

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(person.name);
            try {
                Thread.sleep(555);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(person.name);
        }).start();

        new Thread(() -> {
            System.out.println(person.name);
            person.name = "WangXiaoMing";
            System.out.println(person.name);
        }).start();
    }

}

# 11.2. 使用ThreadLocal

/**
 * ThreadLocal - 线程副本 - 线程隔离特性 - 使用Person类
 * https://www.jianshu.com/p/98b68c97df9b
 * https://www.jianshu.com/p/1a5d288bdaee
 *
 * 下面代码使用ThreadLocal,两个线程读取的不是同一个Person类,使用完必须remove(),
 * 不然会存在内存泄露
 *
 * jvm.reference了解四种引用,ThreadLocal为何内存泄漏
 *
 * 由于ThreadLocalMap的Key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,
 * 发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,
 * 那么这个Entry对象中的Value就有可能一直得不到回收,发生内存泄露
 *
 * 如何避免内存泄露
 * 既然Key是弱引用,那么我们要做的事,就是在使用完ThreadLocal后必须再调用remove()方法,
 * 将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/4/22 17:34
 */
public class T11_ThreadLocal_2 {

    public static class Person {
        public String name = "WangMing";
    }

    // public /*volatile*/ static Person person = new Person();
    public static ThreadLocal<Person> personThreadLocal = new ThreadLocal<Person>() {
        /**
         * 每个线程没值返回的初始化值
         * @return
         */
        @Override
        protected Person initialValue() {
            return new Person();
        }
    };

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(500);
                System.out.println(personThreadLocal.get().name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 最后必须remove()清空值,不然存在内存泄露
                personThreadLocal.remove();
            }
        }).start();

        new Thread(() -> {
            try {
                Person person = new Person();
                person.name = "WangXiaoMing";
                personThreadLocal.set(person);
                System.out.println(personThreadLocal.get().name);
            } finally {
                // 最后必须remove()清空值,不然存在内存泄露
                personThreadLocal.remove();
            }
        }).start();
    }

}

# 11.3. 正确使用ThreadLocal

/**
 * ThreadLocal - 线程副本 - 线程隔离特性 - 使用String
 * https://www.jianshu.com/p/98b68c97df9b
 * https://www.jianshu.com/p/1a5d288bdaee
 *
 * 下面代码使用ThreadLocal,两个线程读取的不是同一个Person类,使用完必须remove(),
 * 不然会存在内存泄露
 *
 * jvm.reference包下了解四种引用,ThreadLocal为何内存泄漏
 *
 * 由于ThreadLocalMap的Key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,
 * 发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,
 * 那么这个Entry对象中的Value就有可能一直得不到回收,发生内存泄露
 *
 * 如何避免内存泄露
 * 既然Key是弱引用,那么我们要做的事,就是在使用完ThreadLocal后必须再调用remove()方法,
 * 将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收
 *
 * @author wliduo[i@dolyw.com]
 * @date 2020/4/22 17:34
 */
public class T11_ThreadLocal_3 {

    public static ThreadLocal<String> personThreadLocal = new ThreadLocal<String>() {
        /**
         * 每个线程没值返回的初始化值
         * @return
         */
        @Override
        protected String initialValue() {
            return "WangMing";
        }
    };

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(600);
                System.out.println(personThreadLocal.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 最后必须remove()清空值,不然存在内存泄露
                personThreadLocal.remove();
            }
        }).start();

        new Thread(() -> {
            try {
                personThreadLocal.set("WangXiaoMing");
                System.out.println(personThreadLocal.get());
            } finally {
                // 最后必须remove()清空值,不然存在内存泄露
                personThreadLocal.remove();
            }
        }).start();
    }

}
上次更新时间: 2023-12-15 03:14:55