返回
Featured image of post ThreadLocal

ThreadLocal

理解ThreadLocal

理解ThreadLocal的关键在于理解 Thread, threadLocals, ThreadLocal 三者的关系

  • threadLocals是Thread中持有的一个实例变量 ThreadLocal.ThreadLocalMap,Thread持有该变量,但是它却是由ThreadLocal负责管理(get/set/remove 方法)其本质上是一个定制化的hash map
1
2
3
4
5
6
7
8
public class Thread implements Runnable
{
    ...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}
  • ThreadLocal 可以理解为线程变量,用来绑定线程与资源(状态),其中最重要的方法是get/set/remove
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

public class ThreadLocal<T> 
{

    ...
    public T get() {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 获取当前线程的threadLocals
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // 从threadLocals map中获取绑定在当前线程的资源
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

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

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
   ...

}

ThreadLocal的使用场景

  • 解决对象的实例变量导致的线程不安全问题 由于ThreadLocal本质上将实例变量进行了拷贝,并与线程绑定,实现了资源的线程隔离,所以不存在实例变量的共享问题,自然也没有线程不安全的问题。这里需要注意的一点是,ThreadLocal 自身在不同线程中是同一个对象,但是我们从中获取的资源却是属于各自不同的线程的。一个恰当的比喻是:Thread比作不同的公交线路,threadLocals就是行驶在不同线路上的公交车- 用来运输该条线路的乘客(资源),而ThreadLocal则是公交线路上的乘车点。虽然同一个乘车点可能会有多条的线路共用,但是同一时间上下车的人仍然属于各自的公交线路。
  • 实现同一线程内的数据传递

ThreadLocal的内存泄露问题

TODO

ThreadLocal 测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.concurrent.CountDownLatch;

@Slf4j
public class ThreadLocalTest
{
    @Test
    public void testThreadLocal() throws InterruptedException
    {
        CountDownLatch latch = new CountDownLatch(2);
        /*
        Junit中测试多线程,主线程结束之后会直接结束,不会等待子线程结束
        解决办法:子线程没结束之前阻塞主线程
            1. 调用join方法,等待子线程结束
            2. 使用CountDownLatch 多线程倒计时计数器,确保一组任务执行完成
            ******************************************************
            1 创建CountDownLatch并设置计数器值。
            2 启动多线程并且调用CountDownLatch实例的countDown()方法。
            3 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,
            直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
            ******************************************************
         */
        //ThreadLocal是同一个对象,但是通过get()方法得到的是绑定在各自线程上的ThreadLocalObj对象
        ThreadLocal<ThreadLocalObj> obj = ThreadLocal.withInitial(ThreadLocalObj::new);
        Thread t1 = new Thread(() -> {
            log.info("t1 running=======");
            log.info(Thread.currentThread().getId() + obj.get().getObjStr());
            log.info("+++++++++++++" + obj);
            log.info("+++++++++++++" + obj.get());
            obj.get().setObjStr("t1 changeId");
            log.info(Thread.currentThread().getId() + obj.get().getObjStr());
            log.info("+++++++++++++" + obj);
            log.info("+++++++++++++" + obj.get());
            latch.countDown();
        });


        Thread t2 = new Thread(() -> {
            log.info("t2 running=======");
            log.info(Thread.currentThread().getId() + obj.get().getObjStr());
            log.info("=============" + obj);
            log.info("=============" + obj.get());
            latch.countDown();
        });
        t1.start();
        // t1.join();
        t2.start();
        latch.await();
        // t2.join();
    }

    @Data
    static class ThreadLocalObj
    {
        String objStr = "ThreadLocalObj";
    }

}

------ output ------
21:59:31.218 [Thread-3] INFO ThreadLocalTest - t1 running=======
21:59:31.218 [Thread-4] INFO ThreadLocalTest - t2 running=======
21:59:31.239 [Thread-3] INFO ThreadLocalTest - 17ThreadLocalObj
21:59:31.239 [Thread-4] INFO ThreadLocalTest - 18ThreadLocalObj
21:59:31.244 [Thread-3] INFO ThreadLocalTest - +++++++++++++java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.244 [Thread-4] INFO ThreadLocalTest - =============java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.250 [Thread-4] INFO ThreadLocalTest - =============ThreadLocalTest.ThreadLocalObj(objStr=ThreadLocalObj)
21:59:31.250 [Thread-3] INFO ThreadLocalTest - +++++++++++++ThreadLocalTest.ThreadLocalObj(objStr=ThreadLocalObj)
21:59:31.252 [Thread-3] INFO ThreadLocalTest - 17t1 changeId
21:59:31.252 [Thread-3] INFO ThreadLocalTest - +++++++++++++java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.252 [Thread-3] INFO ThreadLocalTest - +++++++++++++ThreadLocalTest.ThreadLocalObj(objStr=t1 changeId)
Built with Hugo
Theme Stack designed by Jimmy