【十】Java多线程之 ThreadLocal及其ThreadLocalMap详解和内存泄漏解读

版权声明:转载注明出处 https://blog.csdn.net/jy02268879/article/details/84530582

ThreadLocal

一、简介

不同的线程中,同一个ThreadLocal中的值的对象不一样,且其它 Thread 不可访问。

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

比如,ThreadLocal<StringBuilder>  counter初始时为“”,A线程在其后追加0,对A线程而言counter=0,但是对于B线程而言counter还是初始时候的“”,B线程中读不到A线程对counter的改变。

二、用法

1.创建并初始化(支持泛型)(或者可以使用重写initialValue方法来初始化)

使用

private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(() -> new StringBuilder());

2.set方法

使用

counter.set(str.append(j));

3.get方法

使用 

 StringBuilder str = counter.get();

 三、使用代码示例

1.在主线程中定义ThreadLocal并初始化

2.在主线程中启动3个线程thread-1、thread-2、thread-3,主线程等待这三个线程执行完后统计一共的时间。

3.在线程thread-1、thread-2、thread-3中修改ThreadLocal的值。

package com.sid.threadlocal;

import java.util.concurrent.CountDownLatch;

/**
 * @program: thread-test
 * @description:
 * @author: Sid
 * @date: 2018-11-26 10:38
 * @since: 1.0
 **/
public class ThreadLocalTest {
    private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(() -> new StringBuilder());

    public static void main(String[] args) throws InterruptedException {
        int threads = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threads);
        System.out.printf("main thread start:"+System.currentTimeMillis());
        for(int i = 1; i <= threads; i++) {
            new Thread(() -> {
                for(int j = 0; j < 4; j++) {
                    StringBuilder str = counter.get();
                    counter.set(str.append(j));
                    System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
                            Thread.currentThread().getName(),
                            counter.hashCode(),
                            counter.get().hashCode(),
                            counter.get().toString());
                }
                counter.set(new StringBuilder("hello world"));
                System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s,  Instance hashcode:%s, Value:%s\n",
                        Thread.currentThread().getName(),
                        counter.hashCode(),
                        counter.get().hashCode(),
                        counter.get().toString());
                countDownLatch.countDown();
            }, "thread - " + i).start();
        }
        countDownLatch.await();
        System.out.printf("main thread end:"+System.currentTimeMillis());
    }

}

运行结果

main thread start:1543202951571Thread name:thread - 1 , ThreadLocal hashcode:247001773, Instance hashcode:8326410, Value:0
Thread name:thread - 3 , ThreadLocal hashcode:247001773, Instance hashcode:989188200, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:247001773, Instance hashcode:8326410, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:247001773, Instance hashcode:8326410, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:247001773, Instance hashcode:469347494, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:247001773, Instance hashcode:8326410, Value:0123
Thread name:thread - 3 , ThreadLocal hashcode:247001773, Instance hashcode:989188200, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:247001773, Instance hashcode:989188200, Value:012
Thread name:thread - 3 , ThreadLocal hashcode:247001773, Instance hashcode:989188200, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:247001773,  Instance hashcode:875702598, Value:hello world
Set, Thread name:thread - 1 , ThreadLocal hashcode:247001773,  Instance hashcode:1892279149, Value:hello world
Thread name:thread - 2 , ThreadLocal hashcode:247001773, Instance hashcode:469347494, Value:01
Thread name:thread - 2 , ThreadLocal hashcode:247001773, Instance hashcode:469347494, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:247001773, Instance hashcode:469347494, Value:0123
Set, Thread name:thread - 2 , ThreadLocal hashcode:247001773,  Instance hashcode:356405673, Value:hello world
main thread end:1543202951600

 四、JDK中ThreadLocal的实现

set源码

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

get源码

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 createMap(t,value)是创建了ThreadLocalMap

ThreadLocalMap threadLocals = new ThreadLocalMap(t, value);

getMap(t)方法是返回了ThreadLocalMap。

解读

可以看到ThreadLocal 维护了一个 ThreadLocalMap,key是 Thread,value是它在该 Thread 内的实例。

线程通过该 ThreadLocal 的 get() 获取实例时,只需要以线程为key,从 Map 中找出对应的实例。

那么,ThreadLocalMap跟普通的Map有什么区别呢?

我们来看一下ThreadLocalMap源码主要部分

ThreadLocalMap.Entry的实现

static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

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

Entry的key是个弱引用,Entry的key即ThreadLocal不会发生内存泄漏

内存泄漏

既然Entry的key即ThreadLocal是个弱引用,那么内存泄漏可能会发生在什么地方呢?

内存泄漏可能会发生在Entry或Entry.value。Entry的key即ThreadLocal是个弱引用,垃圾回收器回收的是Entry的key即是ThreadLocal,而不是Entry,更不是Entry.value

ThreadLocalMap使用ThreadLocal的弱引用作为key,
如果一个ThreadLocal没有外部强引用来引用它
那么系统 GC 的时候,这个ThreadLocal就会被回收,
但是Entry.key被回收后,ThreadLocalMap中就会出现key为null的Entry
就没有办法访问这些key为null的Entry.value,
如果当前线程一直不结束,
这些key为null的Entry.value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

此时的Entry.value永远无法回收,这样就有可能发生内存泄漏。

虽然ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些方法设置Entry=null和Entry.value=null

在ThreadLocalMap的getEntry(),set(),remove()的时候都会调用

1.replaceStaleEntry(),将所有Entry.key=null 的 Entry.value设置为null。

2.rehash(),通过 expungeStaleEntry()方法将Entry.key和Entry.value都=null的Entry设置为 null。

但是这样也不能保证不会内存泄漏:

1.使用static的ThreadLocal,延长了ThreadLocal的生命周期。
2.分配使用了ThreadLocal又不再调用ThreadLocalMap的getEntry(),set(),remove()方法。

解决方法:

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

猜你喜欢

转载自blog.csdn.net/jy02268879/article/details/84530582