ThreadLocal 详解

1. ThreadLocal 的底层实现

ThreadLocal

原理

  • ThreadLocal 变量是线程独享的,每个线程都有一个 ThreadLocalMap 变量,该变量存储了 ThreadLocal 实例作为 key,对应的值作为 value。
  • 每个线程(Thread)内部有一个 ThreadLocalMap,而 ThreadLocal 只是一个 key,本身不存储数据。

源码解析

ThreadLocal 通过 ThreadLocalMap 存储数据,以下是 set() 方法的实现:

1
2
3
4
5
6
7
8
9
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
if (map != null) {
map.set(this, value); // 存入数据
} else {
createMap(t, value); // 若不存在,则创建 Map
}
}

get() 方法

1
2
3
4
5
6
7
8
9
10
11
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T) e.value; // 获取 value
}
}
return null;
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

public static void main(String[] args) {
new Thread(() -> {
threadLocal.set(10);
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
}).start();

new Thread(() -> {
threadLocal.set(20);
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
}).start();
}
}

输出

1
2
Thread-0 : 10
Thread-1 : 20

不同线程存储的 threadLocal 值互不影响。


2. ThreadLocal 为什么会导致内存泄漏

原因

ThreadLocalMap 的 key 是弱引用,但 value 是强引用,如果没有手动清除,可能导致 value 无法被回收,从而引发内存泄漏。

如何避免

  1. 使用完手动 remove()
1
threadLocal.remove();
  1. 尽量使用 static 修饰的 ThreadLocal
  2. 减少使用 ThreadLocal 保存大对象

3. sleep() 与 wait() 区别

区别点 sleep() wait()
所属类 Thread Object
是否释放锁 不释放锁 释放锁
使用位置 可在任何地方使用 必须在 synchronized 代码块中
如何唤醒 时间到自动唤醒 notify()notifyAll() 唤醒
线程状态 TIMED_WAITING WAITING

示例

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
public class SleepWaitDemo {
public static void main(String[] args) {
Object lock = new Object();

Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread1: waiting...");
lock.wait(); // 释放锁,进入等待状态
System.out.println("Thread1: resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread2: sleeping...");
Thread.sleep(2000);
System.out.println("Thread2: notifying...");
lock.notify(); // 唤醒 t1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

t1.start();
t2.start();
}
}

输出

1
2
3
4
Thread1: waiting...
Thread2: sleeping...
Thread2: notifying...
Thread1: resumed

t1 释放锁等待,t2 2秒后唤醒 t1


4. 如何保证多个线程顺序执行

方法

  1. 使用 join()
1
2
3
4
5
Thread t1 = new Thread(() -> System.out.println("任务 A"));
Thread t2 = new Thread(() -> System.out.println("任务 B"));
t1.start();
t1.join(); // 等待 t1 执行完毕
t2.start();
  1. 单线程池
1
2
3
4
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("任务 A"));
executor.submit(() -> System.out.println("任务 B"));
executor.shutdown();
  1. CountDownLatch
1
2
3
4
5
6
7
8
9
10
11
12
13
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("任务 A");
latch.countDown();
}).start();
new Thread(() -> {
try {
latch.await();
System.out.println("任务 B");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

5. submit() 和 execute() 区别

executor

区别

区别点 execute() submit()
提交方式 只能提交 Runnable 可提交 RunnableCallable
是否有返回值 有返回值 Future
异常处理 直接抛出异常 需要调用 future.get() 才能获取异常

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ExecutorService executor = Executors.newFixedThreadPool(2);

executor.execute(() -> {
System.out.println("执行任务 A");
});

Future<Integer> future = executor.submit(() -> {
System.out.println("执行任务 B");
return 42;
});

try {
System.out.println("任务 B 结果: " + future.get());
} catch (Exception e) {
e.printStackTrace();
}

executor.shutdown();

输出

1
2
3
执行任务 A
执行任务 B
任务 B 结果: 42

总结
➢ (1) 两个方法都可以向线程池提交任务;
➢ (2) execute只能提交Runnable,无返回值;
➢ (3) submit既可以提交Runnable,返回值为null,也可以提交Callable,返回值Future;
➢ (4) execute()方法定义在Executor接口中;
➢ (5) submit()方法定义在ExecutorService接口中;
➢ (6) execute执行任务时遇到异常会直接抛出;
➢ (7) submit执行任务时遇到异常不会直接抛出,只有在调用Future的get()方法获取返回值时,才会抛出异常;

  • execute() 适用于 不关心返回值 的任务。
  • submit() 适用于 需要获取结果 或 异常处理 的任务。

总结

  • ThreadLocal 通过 ThreadLocalMap 维护线程私有数据,防止多线程共享变量干扰。
  • ThreadLocal 可能导致内存泄漏,要手动 remove() 释放资源。
  • sleep() 不释放锁,wait() 释放锁,wait() 需要 notify() 唤醒。
  • 保证线程顺序执行的方法有 join()、单线程池、CountDownLatch 等。
  • submit() 可返回 Future,execute() 不能返回结果。