ThreadLoacl

三. ThreadLoacl 基础

  1. 在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。 但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。例如类有一个类变量,该类变量会被多个类方法读写,当多线程操作该类的实例对象时,如果线程对类变量有读取、写入操作就会发生类变量读写错误,即便是在类方法前加上synchronized也无效,因为同一个线程在两次调用方法之间时锁是被释放的,这时其它线程可以访问对象的类方法,读取或修改类变量。 这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象,ThreadLocal的作用则恰恰是为了实现线程的不共享,即实现线程所所保留的副本各不冲突的一个作用,(可以理解为是为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程)从而实现并发的另类的一种解决和实现思路,但是ThreadLocal如果不谨慎使用,导致强引用等的关系出现,使其线程始终没被回收,则也会出现内存溢出等的现象

  2. ThreadlLocal 底层通过内部类ThreadLocalMap,实现,会将当前线程Thread作为Key,数据作为值进行存储,ThreadLocalMap中还有一个内部类Entry

  3. ThreadLocal 注意点:

  1. JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
  2. JVM利用调用remove、get、set方法的时候,回收弱引用。
  3. ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链,造成内存泄漏
  4. 当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。
  1. 么如何有效的避免ThreadLocalMap中的key也就是ThreadLocal被垃圾回收为null时造成的内存泄露

a. 在使用完毕后手动调用ThreadLocal的remove方法手动进行释放

  1. 使用示例
  • 存有需要设置线程私有变量的类Res ,该类中有一个计数变量,每次执行累计加1线程私有threadLocal
class Res {
    private Integer count;
    
    /*使用Threadlocal创建一个属于每个线程的变量
    * 只要执行这段代码的线程都会拥有这个变量,变量名就可以看做是Threadlocal对象名
    * 并且每个线程修改这个变量时,修改的是各自的副本,不影响其他线程的使用
    * 创建一个Thradelocal的对象,根据变量的类型,设置Threallocal的泛型类型
    * (变量可以是任何类型,一般为基本类型,或包装类型)对象后设置返回这个变量初始化值的方法
    * (也可以看为是声明一个属于每个线程的变量,初始化为0 */
   public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
      //返回该线程局部变量初始化值
      protected Integer initialValue() {
         return 0;
      };
   };
   
   //调用该方法,设置线程的局部变量累计+1,然后返回(先获取局部变量+1后再设置到局部变量里)
   public Integer getNum(){
        //获当前线程中局部变量值 然后+1
       Integer count= threadLocal.get()+1;
       //把+1后的count在设置给属于每个线程的变量
        threadLocal.set(count);
        //再次获取修改后的属于每个线程的变量
       return  threadLocal.get();
    }

	public void remove(ThreadLocal<Integer> threadLocal){
		//删除线程中的私有变量
		threadLocal.remove();
	}
  • 调用测试
public class ThreadLocaDemo2 extends Thread {
    private Res res;
    //初始化线程对象需要Res对象,因为在线程对象中
    //的run方法中要调用Res的方法,进而对Res中的某个
    //方法实现多线程
    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }
    @Override//run方法中调用Res中的getNum方法
    public void run() {
        //循环调用getNum方法(getNum方法每执行一次,对执行这个方法的线程
        // 的局部变量+1,然后返回调用这个方法的线程的局部变量)
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + res.getNum());
        }
    }
    public static void main(String[] args) {
        //创建Res对象,将res对象传入ThreadLocaDemo2中,创建自定义线程类对象
        Res res = new Res();
        ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);//线程1
        ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);//线程2
        ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);//线程3
        //运行线程1.会自动运行run方法,循环调用res中的getNum方法,
        // 获取到线程1的局部变量
        threadLocaDemo1.start();
        //运行线程2.会自动运行run方法,循环调用res中的getNum方法,
        // 获取到线程2的局部变量
        threadLocaDemo2.start();
      /*查看运行结果,会发现,虽然线程1在运行后循环调用了getNum
      * 最终把线程1的变量修改为了3,
      * 当线程2在去运行,循环调用getNum时,局部变量还是原来的0开始
      * 线程1的修改不会影响到线程2*/

	
    }
}

二. InheritableThreadLocal

  1. 在某些时刻需要考虑ThreadLocal的传递问题,使用InheritableThreadLocal
  2. Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中 inheritableThreadLocals 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap
public class Thread implements Runnable {
   //......(其他源码)
     
    //当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
    ThreadLocal.ThreadLocalMap threadLocals = null;

    
    //InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
    //主要用于父子线程间ThreadLocal变量的传递
    //本文主要讨论的就是这个ThreadLocalMap
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......(其他源码)
}
  1. InheritableThreadLocal类重写了ThreadLocal的3个方法
   
    //该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
    protected T childValue(T parentValue) {
        return parentValue;
    }

     //由于重写了getMap,操作InheritableThreadLocal时,
     //将只影响Thread类中的inheritableThreadLocals变量,
     //与threadLocals变量不再有关系
     ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

	  
    //类似于getMap,操作InheritableThreadLocal时,
    //将只影响Thread类中的inheritableThreadLocals变量,
    //与threadLocals变量不再有关系
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
  1. Thread初始化可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程
	//默认情况下,设置inheritThreadLocals可传递
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
	
	
    //初始化一个线程.此函数有两处调用,
    //1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true
    //2、传递AccessControlContext,inheritThreadLocals=false
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //......(其他代码)

        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        //......(其他代码)
    }
  1. ThreadLocal.createInheritedMap中的createInheritedMap
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

	//构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
    //该函数只被 createInheritedMap() 调用.
    private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            // ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
            table = new Entry[len];

            // 逐一复制 parentMap 的记录
            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) {
                        // 可能会有同学好奇此处为何使用childValue,而不是直接赋值,
                        // 毕竟childValue内部也是直接将e.value返回;
                        // 个人理解,主要为了减轻阅读代码的难度
                        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++;
                    }
                }
            }
  1. InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递

三. TransmittableThreadLocal

  1. TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable 使用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值