【Java并发】- 8.对并发工具类ThreadLocal的源码解析

文章目录

      • 1.简介
      • 2.ThreadLocal的使用
      • 3.对ThreadLocal的主要方法的源码解析
        • ThreadLocalMap
        • set方法
        • get方法
      • 4.ThreadLocal中的内存泄漏问题

1.简介

ThreadLocal是一个关于创建线程局部变量的类。

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全 规避了多线程的并发问题。

注意在使用ThreadLocal时最好把其说明成static以方便其他类直接使用,因为ThreadLocal是伴随着线程的存在而存在的。

2.ThreadLocal的使用

public class ThreadLockDemo {
    private static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
        // 实现initialValue()
        public Integer initialValue() {
            return 0;
        }
    };

    public int increase(){
        local.set(local.get() + 1);
        return local.get();
    }

    public static void main(String[] args) {
        TestThread testThread = new TestThread(new ThreadLockDemo());
        for (int i = 0; i < 4; i++) {
            new Thread(testThread).start();
        }
    }
}

class TestThread implements Runnable{
    private ThreadLockDemo demo ;

    public TestThread(ThreadLockDemo demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + demo.increase());
        }
    }
}

输出结果:

Thread-0:1
Thread-2:1
Thread-2:2
Thread-1:1
Thread-2:3
Thread-3:1
Thread-0:2
Thread-3:2
Thread-1:2
Thread-3:3
Thread-0:3
Thread-1:3

可以看到,各个线程中的ThreadLocal互不影响

3.对ThreadLocal的主要方法的源码解析

在对ThreadLocal进行解析之前,我们先来看看与ThreadLocal相关的几个内部类。

ThreadLocalMap

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }/**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
}

在ThreadLocalMap可以看到有一个静态内部类Entry这是存放键值对的位置,这个类就是ThreadLocal的set方法set值存放的位置,存放在value,而根据其构造方法可以看到,这个类存放的建是一个ThreadLocal类型的数据

2.根据其table参数可以看到ThreadLocalMap在内部存放一个Entry数组来维护多线程环境下每个线程的ThreadLocal中存放的值。

set方法

public void set(T value) {
		//获取当前线程
        Thread t = Thread.currentThread();
        //获取线程中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //map不为空,说明这个线程有对应的
        //ThreadLocal的值,调用ThreadLocalMap 中对set方法
        //进行设置;若为空,则设置一个ThreadLocalMap赋给Thread对象
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

//根据线程对象获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

//创建一个ThreadLocalMap,并且把当前的ThreadLocal对象作为值传进去
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

//ThreadLocalMap 中对set方法
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         //对tab进行+1的循环遍历,如果到数组尾部,把i设置为0,继续遍历
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

		//获取在ThreadLocalMap中存放Entry中的ThreadLocal对象
		//如果存放的对象与传入的相等,则直接把vlaue值替换掉
        if (k == key) {
            e.value = value;
            return;
        }
		//key为null,说明之前存放的的ThreadLocalMap被回收了
		//就清除对应的value,因为ThreadLocalMap是弱引用
		//会在下一次垃圾回收时,被回收,但其value不保证被回收
		//故用replaceStaleEntry实现回收,防止内存泄漏
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

//如果Thread的ThreadLocalMap中没有传入的
//ThreadLocal对象就把新的对象及其value放入tab的数组中。
    tab[i] = new Entry(key, value);
    int sz = ++size;
     // cleanSomeSlots 清楚陈旧的Entry(key == null)
    // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

set方法会把ThreadLocal与其存放的value放入Thread对象的ThreadLocalMap的Entry数组中。

由此可以看出ThreadLocal的设计思想是:在线程对象Thread中设置ThreadLocalMap对象,来存放ThreadLocal及其在线程中设置的value,并且把ThreadLocal作为键存放,因为每个线程的ThreadLocalMap对象都不一样,所有不会引起冲突。而ThreadLocalMap中的Entry采用弱引用保证了会被及时回收。因为每个线程都给当前ThreadLocal的副本从而确保了,每个线程的ThreadLocal中的值相互不影响。

get方法

public T get() {
	//同set方法开始一样先获取当前Thread对象中的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    //如果map有值,就获取map中当前ThreadLocal作为键的Entry对象
    //并且判断成功后,把Entry对象中的value直接返回出去
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果Thread的map不存在就初始化一个
    return setInitialValue();
}

private T setInitialValue() {
//初始化是给map写入一个value这个initialValue可以有程序员自己重写
//其他操作和set方法类似
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

get方法会先尝试获取当前ThreadLocal对象的value,如果当前的ThreadLocal没设置value,那么就会调用其setInitialValue方法调用initialValue()方法设置一个初始值,并且把这个值返回,initialValue()可以由开发者自己进行重写。

4.ThreadLocal中的内存泄漏问题

从3对ThreadLocal的源码分析可以得出这样一个图
【Java并发】- 8.对并发工具类ThreadLocal的源码解析_第1张图片
这里采用了弱引用的方式解决了ThreadLocal的内存泄漏(即存在于Thread的ThreadLocal不会吧回收从而造成内存溢出)。那些在栈中ThreadLocal的引用如果被删除,那么对象就只有一个弱引用会在下一次垃圾回收时被回收。

但是仅仅依赖弱引用仍然不能完全解决内存泄漏问题,因为Entry是一个键值对,键被回收,但值依旧存在,仍然会造成内存泄漏。所以ThreadLocal类的设计人员在代码中也加入了key为null时的回收处理,。如在set的源码分析中就有体现,在get方法中也有。当然如果想更好的进行回收,可以显示调用ThreadLocal的remove()方法进行处理。

你可能感兴趣的:(Java并发)