阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

ThreadLocal的正确使用与原理

89次阅读
没有评论

共计 4033 个字符,预计需要花费 11 分钟才能阅读完成。

ThreadLocal 是什么

ThreadLocal 是线程 Thread 中属性 threadLocals 即 ThreadLocal.ThreadLocalMap 的管理者,ThreadLocal 用于给每个线程操作自己线程的本地变量,通过线程私有从而保证线程安全性。

ThreadLocal 原理

拿 get() 方法来说,线程的本地变量是存放在线程实例的属性 ThreadLocalMap 上的,ThreadLocalMap 本质上就是一个 HashMap,ThreadLocal 只是一个管理者,当我们的线程需要拿到自己的本地变量时,我们直接调用 ThreadLocal 去 get 本地变量即可。
因为 get() 方法底层会先获取到当前线程,然后通过当前线程拿到他的属性值 ThreadLocalMap,如果 ThreadLocalMap 为空,则会调用 ThreadLocal 的初始化方法拿到初始值返回,如果不为空,则会拿该 ThreadLocal 作为 key 去获取该线程下的 ThreadLocalMap 里对应的 value 值。

ThreadLocal 内存泄漏问题

线程的属性值 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用, 而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样的话,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
因此针对这种情况,我们有两种原则:
ThreadLocal 申明为 private static final。JDK 建议 ThreadLocal 定义为 private static,这样 ThreadLocal 的弱引用问题则不存在了。private 与 final 尽可能不让他人修改变更引用。static 表示为类属性,只有在程序结束才会被回收。ThreadLocal 使用后务必调用 remove 方法。
最简单有效的方法是使用后将其移除。

关于 InheritableThreadLocal

InheritableThreadLocal 类是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。

ThreadLocal 使用

public class ThreadLocalTest {

    // 第一种初始化方式
    /**
     * 声明为 static 是让 ThreadLocal 实例随着程序的结束才结束,这样才不会让 GC 回收了
     * 声明为 final 是让 ThreadLocal 实例引用不会被替换,这样子也不会因为被替换导致被 GC 回收
     * 这两个声明都是为了避免作为 key 的 ThreadLocal 对象没有外部强引用而导致被 GC 回收,从而导致内存泄漏的问题,因为 ThreadLocalMap 中的 ThreadLocal
     * 对象作为 key 是弱引用,会被 GC 回收。*/
    private static final ThreadLocal threadLocalStr = ThreadLocal.withInitial(() -> "fresh");

    private static AtomicInteger intGen = new AtomicInteger(0);
    // 第二种初始化方式
    private static final ThreadLocal threadLocalInt = new ThreadLocal() {
        @Override
        public Integer initialValue() {return intGen.incrementAndGet();
        }
    };

    public static void main(String[] args) throws InterruptedException {ArrayList threads = new ArrayList();
        for (int i = 0; i  {
                try {System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                    TimeUnit.SECONDS.sleep(5);
                    threadLocalStr.set("bojack horseman" + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                } catch (InterruptedException e) {e.printStackTrace();
                } finally {threadLocalInt.remove();
                    threadLocalStr.remove();}
            });
            t.start();
            threads.add(t);
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(threads);
        System.out.println(threadLocalStr);
        System.out.println(threadLocalInt);
    }
    /**
     * Thread-0   1
     * Thread-1   2
     * Thread-0   fresh
     * Thread-1   fresh
     * [Thread[Thread-0,5,main], Thread[Thread-1,5,main]]
     * java.lang.ThreadLocal$SuppliedThreadLocal@1ef7fe8e
     * cn.vv.schedule.test.ThreadLocalTest$1@6f79caec
     * Thread-1   2
     * Thread-1   bojack horseman2
     * Thread-0   1
     * Thread-0   bojack horseman1
     */

}

InheritableThreadLocal 使用

public class InheritableThreadLocalTest {

    // 第一种初始化方式
    private static final InheritableThreadLocal threadLocalStr = new InheritableThreadLocal() {
        @Override
        public String initialValue() {return "fresh";}
    };
    private static AtomicInteger intGen = new AtomicInteger(0);
    // 第二种初始化方式
    private static final ThreadLocal threadLocalInt = new ThreadLocal() {
        @Override
        public Integer initialValue() {return intGen.incrementAndGet();
        }
    };

    public static void main(String[] args) throws InterruptedException {
        // 如果是 InheritableThreadLocal,则父线程创建的所有子线程都会复制一份父线程的线程变量,而不是去初始化一份线程变量
        threadLocalStr.set("main");
        ArrayList threads = new ArrayList();
        for (int i = 0; i  {
                try {System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                    TimeUnit.SECONDS.sleep(5);
                    // 子线程可以自由地改变自己的本地变量
                    threadLocalStr.set("bojack horseman" + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalInt.get());
                    System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
                } catch (InterruptedException e) {e.printStackTrace();
                } finally {threadLocalInt.remove();
                    threadLocalStr.remove();}
            });
            t.start();
            threads.add(t);
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName() + " " + threadLocalStr.get());
    }
    /**
     * Thread-0   2
     * Thread-1   1
     * Thread-0   main
     * Thread-1   main
     * main   main
     * Thread-0   2
     * Thread-0   bojack horseman2
     * Thread-1   1
     * Thread-1   bojack horseman1
     */

}

阿里云 2 核 2G 服务器 3M 带宽 61 元 1 年,有高配

腾讯云新客低至 82 元 / 年,老客户 99 元 / 年

代金券:在阿里云专用满减优惠券

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-07-25发表,共计4033字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中