首页 > java线程 > ThreadLocal使用场景分析,实现原理

ThreadLocal使用场景分析,实现原理

作者:bin
目录
[隐藏]

ThreadLocal和Synchonized都是为了解决线程并发的问题,但是ThreadLocal和Synchonized是完全相反的
ThreadLocal是为每个线程提供变量的副本,自己私有的变量
Synchonized是控制共享的变量只能在某个时间被一个线程用到

1.使用场景

例如给线程设置一个threadID:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DemoTask implements Runnable {
    // 包含要分配的下一个线程ID的原子整数
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // 包含每个线程的ID的ThreadLocal变量
    private static final ThreadLocal threadId = new ThreadLocal() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();// 获取值,并递增
        }
    };

    public void run() {
        System.out.printf("Starting Thread: %s : %s\n", threadId.get());
        try {
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Thread Finished: %s : %s\n",  threadId.get());
    }
}

输出的内容如下:

Starting Thread: 0
Starting Thread: 1
Starting Thread: 2
Thread Finished: 0
Thread Finished: 1
Thread Finished: 2

为了对比,我们不使用,ThreadLocal变量创建线程id试试

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DemoTask implements Runnable {
    // 包含要分配的下一个线程ID的原子整数
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final Integer threadId = nextId.getAndIncrement();
    public void run() {
        System.out.printf("Starting Thread: %s\n", threadId);
        try {
            TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
//            startDate.remove();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Thread Finished: %s\n",  threadId);
    }
}

输出的内容如下:

Starting Thread: 0
Starting Thread: 0
Thread Finished: 0
Starting Thread: 0
Thread Finished: 0
Thread Finished: 0

2.实现原理

实际上ThreadLocal的所有变量,是保存在当前线程实例中,一个类似map的属性中

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap跟当前线程绑定

Thread t = Thread.currentThread();

而这个ThreadLocalMap实际上是一个数组

 Entry[] tab = table;

数组中存了Entry对象,Entry对象的key就是ThreadLocal,value是我们设置的值

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap.set中通过对ThreadLocal的hash确定槽位

 int i = key.threadLocalHashCode & (len-1);

ThreadLocalMap.set(ThreadLocal key, Object value)代码如下:

  
public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //从Thread.threadLocals中获取值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
//map.set(this, value);
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //确定hash槽位
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         //开放地址
         e = tab[i = nextIndex(i, len)]) {
          ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            //弱引用被回收进行替换
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //插入
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

ThreadLocalMap.get()方法

public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //从Thread.threadLocals中获取值
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //this指private static final ThreadLocal threadId = new ThreadLocal()对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
 }

3.ThreadLocal中的弱引用

其中ThreadLocalMap中的Entry使用的是弱引用,这个很好理解,如果Entry强引用ThredLocal,那么他在线程执行完当前任务后,不会被回收,只会在线程被回时才会被回收。

   static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

4.ThreadLocal 内存泄漏的原因分析

我们来看下图内存模型currentThread强引用了threadLocalMap,threadLocalMap中Entry的,key弱引用了threadLoacl,Entry中value强引用了threadLocal对应的值

如果程序代码执行完毕,Entry的key-threadLocal因为是弱引用,就会被GC回收,这时threadLocalMap这个key-null,变成了null,但是对应的value因为还是强引用在threadLocalMap中,所以不会被GC回收,就会造成内存泄漏

ThreadLocalMap如何解决这个问题的呢?
在get、set、remove方法中都有将key为null的情况清理掉。
其中get调用expungeStaleEntry

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    //清理失效的值
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

set中如果发现key是null,就直接调用replaceStaleEntry方法覆盖

     private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    //值为null,将其覆盖,老的值被GC
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

remove中调用clear()方法清楚关联,同时调用expungeStaleEntry清理

  /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            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;
                }
            }
        }

如何避免泄露:
我们在使用ThreadLocal时,尽量在使用完后,手动去remove(),就可以有效的避免内存泄漏

您必须 [ 登录 ] 才能发表留言!