认识Java里面的Thread一一探秘ThreadLocal

 此系列文章是参考《JAVA并发编程从入门到精通》一书写的一些读后笔记,其中也会进行扩展补充,写的不准确的地方还望广大同胞指出,大家一起学习,一起码奴。

ThreadLocal是个什么东西

 ThreadLocal为每个使用该变量的线程提供独立的变量副本,就是说每个线程都可以独立地改变自己的副本,而不会影响其他的线程。从线程的角度来看,目标变量就像是线程的本地变量,即ThreadLocal

使用ThreadLocal的简单示例

public class ThreadLocalTest extends Thread{
    //1.通过匿名内部类覆盖ThreadLocal的initialValue方法,指定初始值
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    //2.获取下一个序列值
    public Integer getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    @Override
    public void run() {
        //3.三次循环输出线程的名称以及seqNum参数值
        for (int i = 0; i < 3 ; i++){
            System.out.println(
                    String.format("threaName : %s , seqNum = %s",
                            Thread.currentThread().getName(),getNextNum()));
        }
    }

    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        //4.启动三个线程共享ThreadLocalTest,各自产生序列号
        Thread thread1 = new Thread(threadLocalTest);
        Thread thread2 = new Thread(threadLocalTest);
        Thread thread3 = new Thread(threadLocalTest);
        thread1.start();
        thread2.start();
        thread3.start();
    }

}

程序的运行结果如下:

threadName = Thread-2 , seqNum = 1
threadName = Thread-3 , seqNum = 1
threadName = Thread-1 , seqNum = 1
threadName = Thread-3 , seqNum = 2
threadName = Thread-1 , seqNum = 2
threadName = Thread-3 , seqNum = 3
threadName = Thread-1 , seqNum = 3
threadName = Thread-2 , seqNum = 2
threadName = Thread-2 , seqNum = 3

我们会发现三个线程共享同一个ThreadLocalTest实例,但没有互相干扰的情况,而是各自产生独立的序列号,这就是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

ThreadLocal的常用方法们

  1. void set(T value) :设置当前线程的局部变量的值
	public void set(T value) {
	   //1.获取当前线程Thread对象
       Thread t = Thread.currentThread();
       //2.根据thread对象获取ThreadLocalMap对象
       ThreadLocalMap map = getMap(t);
       if (map != null)
           map.set(this, value);//3.将当前对象与value存入ThreadLocalMap中
       else
           createMap(t, value); //4.创建一个ThreadLocalMap
   	}

 从上面的set方法源码中可以看出,TheadLocal存储value其实是靠ThreadLocalMap去做的,保证了线程独立的变量也是ThreadLocalMap施的魔法,下面来看下上个代码片段中出现的方法。

	//只是做了获取Thead的threadLocals属性
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    //创建一个ThreadLocalMap
	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

	//ThreadLocalMap其实是ThreadLocal的一个静态内部类
	static class ThreadLocalMap {
		//ThreadLocalMap提供的有参构造器
		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
			//初始化一个Entry数组,INITIAL_CAPACITY默认容量16
            table = new Entry[INITIAL_CAPACITY];
            //计算该元素在数组中的下标
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //通过ThreadLocal,value构造Entry对象,放入table数组中
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            //计算下次数组多大时进行resize,并赋值给Threshold
            setThreshold(INITIAL_CAPACITY);
        }
	}

 上面这一段可以看出,Thread的局部变量threadLocals是一个ThreadLocalMap,也就是说线程的局部变量都会保存在ThreadLocalMap里面,而TheadLocalMap又是如何在保存变量的呢,其实就是由ThreadLocalMap的内部类Entry[]来完成的,而Entry中的key就是threadlocal对象,value就是我们传入的值。至此可以细细的品味下这样的设计逻辑,而且还有个点需要思考的就是,Entry将ThreadLocal设置为了弱引用是为了什么。

  1. public T get() :返回当前线程所对应的线程局部变量
	//获取线程的局部变量
	public T get() {
        Thread t = Thread.currentThread();
        //根据当前的Thread对象获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//通过ThreadLocal从ThreadLocalMap中获取到Entry的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 可以看出获取线程的局部变量值是通过map.getEntry(this)来完成,上文ThreadLocalMap构造器中可以看到Entry是怎么生成的了,所以这里就不用源码来解释怎么获取的了,下面我们再看setInitialValue()做了什么:

	//这个方法的作用就是设置一个初始值,默认是null,方法与set()基本一直,只不过value不是参数而已
	private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
  1. public void remove() :将当前局部变量的值删除
	//ThreadLocal删除局部变量的值
	public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
     
	//ThreadLocalMap的remove方法
	private void remove(ThreadLocal<?> key) {
          Entry[] tab = table;
          int len = tab.length;
          	//获取变量值在Entry[]数组的下标
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //清楚元素
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
      }
  1. protected T intialValue() :返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,这个方法是一个延迟调用方法,缺省实现是直接返回一个null

ThreadLocal的数据结构

在这里插入图片描述

  • 每个Thread线程内部都有一个ThreadLocalMap
  • ThreadLocalMap有一个Entry数组,数组元素里面存储线程本地对象(key)和线程的变量副本(value)
  • Thread内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

注意点

  1. 在ThreadLocalMap内部类Entry是继承了WeakReference,也就说Entry被设置成了弱引用,那么在垃圾回收时key为null的Entry将会被回收掉
  2. ThreadLocal什么时候会导致内存泄漏
  • 由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,如果当前线程再迟迟不结束,这些value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
  • 其实ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value,但以下的情况还是会又内存泄漏的隐患:
      1.使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
      2.分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。
  1. 在使用线程池的时候,ThreadLocal的使用要更为小心
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值