Spring笔记(十一)—— 事务管理之 ThreaLocal

按照传统的经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用 synchronized 进行线程同步,但线程同步机制会降低并发性,影响系统性能。ThreaLocal 可以改变这种方式。

认识

JDK 1.2 版本开始提供 java.lang.ThreaLocal,ThreaLocal 为解决多线程程序的并发问题提供了新的思路。ThreaLocal 是线程的一个本地化对象,当工作于多线程中的对象使用 ThreadLocal 维护变量时,它会为每个使用该变量的线程分配一个独立的变量副本。所有每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本;从线程的角度看,这个变量就像是线程的本地变量。

接口方法

ThreadLocal 支持泛型,且有四个简单的接口:

  • void set(T value):设置当前线程的线程局部变量的值。
  • public T get():该方法返回当前线程所对应的线程局部变量。
  • public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用(JDK 5.0 新增)。当线程结束后,对应该线程的局部变量将会自动被垃圾回收,所以显示调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected T initialValue():返回该线程局部变量的初始值,该方法是一个 protected 方法,为了让子类覆盖而设计。

ThreadLocal 的实现思路:在 ThreadLocal 内部有一个 Map,用于存储每一个线程的变量副本,Map 中的元素的 key 为线程对象,value 为对应线程的变量副本。

实例

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
public class SequenceNumber {
private static class TestClient extends Thread {
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("Thread[" + Thread.currentThread().getName()
+ "] sn[" + sn.getNextNum() + "]")
}
}
}
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}

运行结果:

1
2
3
4
5
6
7
8
9
Thread[Thread-2] sn[1]
Thread[Thread-0] sn[1]
Thread[Thread-1] sn[1]
Thread[Thread-2] sn[2]
Thread[Thread-0] sn[2]
Thread[Thread-1] sn[2]
Thread[Thread-2] sn[3]
Thread[Thread-0] sn[3]
Thread[Thread-1] sn[3]

从输出结果上看,由于 ThreadLocal 为每一个线程提供了单独的副本,所以每个线程所产生的序号虽然都共享同一个 SequenceNumber 实例,但它们没有发生相互干扰的情况,而是各自产生独立的序列号。

与线程同步机制的比较

在线程同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序解决什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等复杂的问题。
ThreadLocal 从另一个角度解决多线程的并发访问,为每个线程提供独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对变量进行同步。ThreadLocal 提供了线程安全的对象封装,在编写多线程代码时,可以把部安全的变量封装进 ThreadLocal。
总的来说,同步机制采用“以时间换空间”的方式:访问串行化,对象共享化;而 ThreadLocal 采用“以空间换时间”的方式:访问并行化,对象独享化。

参考资料:

Spring 3.x 企业应用开发实战

热评文章