ThreadLocal 详解

把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
threadLocal.remove();
});
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
threadLocal.remove();
});
thread1.start();
thread2.start();
}
}
// 1

原理

ThreadLocal#set:把值存到当前线程的 ThreadLocalMap 中

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
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}

ThreadLocalMap getMap(Thread t) {
// 线程中的 ThreadLocalMap 类型字段
return t.threadLocals;
}

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

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

内存泄露

ThreadLocal 对象 new 后有强引用,而当前线程中的 ThreadLocalMap 对象的键也有 ThreadLocal 对象的弱引用,所以当 ThreadLocal 对象失去强引用时 ThreadLocalMap 中对应的键也会变为 null,防止了内存泄露。

虽然 ThreadLocalMap 的键为 null 了,但是其 value 值还存在所以依然会有内存泄露,所以需要执行 ThreadLocal#remove 方法。
Java 技术之 AQS 详解

内部维护一个 state 和一个双向线程链表

  1. ReentrantLock#lock
    1. AbstractQueuedSynchronizer#compareAndSetState:CAS 把 state 从 0 变为 1,若成功则代表拿到锁

    2. AbstractOwnableSynchronizer#setExclusiveOwnerThread:若抢到锁,则设置当前线程为独占线程

    3. AbstractQueuedSynchronizer#acquire

      1
      2
      3
      4
      5
      public final void acquire(int arg) {
      if (!tryAcquire(arg) &&
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      selfInterrupt();
      }
    4. ReentrantLock.NonfairSync#tryAcquire
      1. ReentrantLock.Sync#nonfairTryAcquire:

      • state 为 0,则继续调用 compareAndSetState 抢锁(尝试把 state 变为 1,成功则接着调用 setExclusiveOwnerThread)
      • state 为 > 0,且当前线程是独占访问的那个线程(说明锁重入),则 state++(此时 state 代表重入线程数)
      • 否则 TryAcquire 失败
    5. TryAcquire 失败则调用 AbstractQueuedSynchronizer#addWaiter:使用 CAS 加入链表队列

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
// jdk 8
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
// jdk 9 使用 VarHandler.set(this, pred)。
// VarHandler 内部有 CAS 的方法
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}


jdk 9 使用 VarHandler.set(this, pred) 代替 node.prev = pred;,其调用 native 实现(相当于直接操纵二进制码),效率比反射高
VarHandler 指向一个变量

     1. AbstractQueuedSynchronizer#acquireQueued:加入队列后不断监听前一个节点,若前节点为头结点(已拿到锁),则试图去抢锁,成功则返回 false(不中断)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

参考