美文网首页
ThreadLocal的理解

ThreadLocal的理解

作者: 手扶拖拉机_6e4d | 来源:发表于2021-09-05 00:10 被阅读0次
    • 1.ThreadLocal是什么?

    首先介绍Thread类中属性threadLocals:

    image.png
    我们发现Thread并没有提供成员变量threadLocals的设置与访问的方法,那么每个线程的实例threadLocals参数我们如何操作呢?这时我们的主角:ThreadLocal就登场了
    ThreadLocal是线程Thread中属性threadLocals的管理者
    也就是说我们对于ThreadLocalget, setremove的操作结果都是针对当前线程Thread实例threadLocals存,取,删除操作。
    • 2.ThreadLocal的应用场景

    多数据源切换、Spring声明式事务

    使用案例:

    为了更直观的体会ThreadLocal的使用我们假设如下场景

      1. 我们给每个线程生成一个ID。
      1. 一旦设置,线程生命周期内不可变化。
      1. 容器活动期间不可以生成重复的ID
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ThreadLocalId {
    
        public static final AtomicInteger nextId = new AtomicInteger(0);
    
        public static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return nextId.getAndIncrement();
            }
        };
    
        public static int get(){
            return threadId.get();
        }
    
        public static void remove(){
            threadId.remove();
        }
    }
    

    测试程序如下:我们同一个线程不断get,测试id是否变化,同时测试完成后我们就将其释放掉

     public static void main(String[] args) {
            incrementSameThreadId();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    incrementSameThreadId();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    incrementSameThreadId();
                }
            }).start();
        }
    
        private static void incrementSameThreadId(){
            try{
                for (int i=0; i<5; i++){
                    System.out.println(Thread.currentThread() + "_" + i + ", threadId:" + ThreadLocalId.get());
                }
            } finally {
                ThreadLocalId.remove();
            }
        }
    

    输出结果:
    Thread[main,5,main]_0, threadId:0
    Thread[main,5,main]_1, threadId:0
    Thread[main,5,main]_2, threadId:0
    Thread[main,5,main]_3, threadId:0
    Thread[main,5,main]_4, threadId:0
    Thread[Thread-1,5,main]_0, threadId:1
    Thread[Thread-1,5,main]_1, threadId:1
    Thread[Thread-1,5,main]_2, threadId:1
    Thread[Thread-1,5,main]_3, threadId:1
    Thread[Thread-1,5,main]_4, threadId:1
    Thread[Thread-2,5,main]_0, threadId:2
    Thread[Thread-2,5,main]_1, threadId:2
    Thread[Thread-2,5,main]_2, threadId:2
    Thread[Thread-2,5,main]_3, threadId:2
    Thread[Thread-2,5,main]_4, threadId:2
    结果:确实是不同线程间id不同,相同线程id相同

    • ThreadLocal原理
      1. ThreadLocal类结构及方法解析
        image.png
        上图可知:ThreadLocal三个方法get, set , remove以及内部类ThreadLocalMap
      1. ThreadLocal及Thread之间关系
        image.png
        从这张图我们可以直观的看到Thread中属性threadLocals,作为一个特殊的Map,它的key值就是我们ThreadLocal实例,而value值这是我们设置的值
    • ThreadLocal操作过程

    我们以get方法为例

    image.png
    其中getMap(t)返回的就上当前线程的threadlocals,如下图,然后根据当前ThreadLocal实例对象作为key获取ThreadLocalMap中的value,如果首次进来这调用setInitialValue()
    image.png
    image.png
    set过程也类似
    image.png
    注意:ThreadLocal中可以直接t.threadLocals是因为ThreadThreadLocal在同一个包下,同样Thread可以直接访问ThreadLocal.ThreadLocalMap threadLocals = null;来进行声明属性
    • ThreadLocal使用避免那些坑

    我们来看下为什么说ThreadLocal会引起内存泄漏,什么场景下会导致内存泄漏?
    先回顾下什么叫内存泄漏,对应的什么叫内存溢出
    Memory overflow:内存溢出,没有足够的内存提供申请者使用。
    Memory leak:内存泄漏,程序申请内存后,无法释放已申请的内存空间,内存泄漏的堆积终将导致内存溢出。
    显然是TreadLocal在不规范使用的情况下导致了内存没有释放。

    image.png
    我们看到了一个特殊的类WeakReference,同样这个类,应用开发者也同样很少使用,这里简单介绍下吧
    image.png
    既然WeakReference下一次gc即将被回收,那么我们的程序为什么没有出问题呢?
    • 测试弱引用的回收机制
    // 情形1: 存在强引用不会被回收。
      public static void main(String[] args) {
    
            String str = new String("test ThreadLocal leak!");
            WeakReference<String> weakReference = new WeakReference<String>(str);
            System.gc();
    
            if (weakReference.get() == null){
                System.out.println("weak reference被GC回收");
            }else {
                System.out.println(weakReference.get()); 
              //输出: test ThreadLocal leak!
            }
    
        }
    
    // 情形2: 没有强引用将会被回收
     public static void main(String[] args) {
            WeakReference<String> weakReference = new WeakReference<String>(new String("test ThreadLocal leak!"));
            System.gc();
    
            if (weakReference.get() == null){
                System.out.println("weak reference被GC回收");
            }else {
                System.out.println(weakReference.get());
            }
        }
    

    我们看下ThreadLocal的弱引用回收情况:

    image.png
    如上图所示,我们在作为keyThreadLocal对象没有外部强引用,下一次gc必将产生key值为null的数据,若线程没有及时结束必然出现,一条强引用链Threadref–>Thread–>ThreadLocalMap–>Entry,所以这将导致内存泄漏

    使用ThreadLocal时会发生内存泄漏的前提条件:
    ThreadLocal引用被设置为null,且后面没有set,get,remove操作。
    ②线程一直运行,不停止。(线程池)
    ③触发了垃圾回收。(Minor GC或Full GC)
    我们看到ThreadLocal出现内存泄漏条件还是很苛刻的,所以我们只要破坏其中一个条件就可以避免内存泄漏,单但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则:
    ThreadLocal申明为private static final
    Private与final 尽可能不让他人修改变更引用,
    Static 表示为类属性,只有在程序结束才会被回收。
    ThreadLocal使用后务必调用remove方法。
    最简单有效的方法是使用后将其移除。

    参考文章:https://mp.weixin.qq.com/s?__biz=MzUxOTc4NjEyMw==&mid=2247516621&idx=2&sn=6df983cd09f1c90121937f8280079c84&chksm=f9f69a29ce81133f49834cdc69a325eecbf308c85cc6c0ebdd79b7f09c75264e9f468f4ebcd9&mpshare=1&scene=23&srcid=0831VbYborv19WxQsU8KgVAt&sharer_sharetime=1630388638293&sharer_shareid=f770d25bc57f1c2f9159f85750f854dc#rd

    相关文章

      网友评论

          本文标题:ThreadLocal的理解

          本文链接:https://www.haomeiwen.com/subject/azvdwltx.html