(本文摘抄Spring3.X企业应用开发实战)
解决多线程中相同变量的并发访问冲突问题,通常是采用synchronized方式,即通过对象锁机制保证同一时间只有一个线程访问变量。但是,使用同步机制需要缜密分析什么时候对变量进行读写,锁定对象,释放对象,对编程要求高。
JDK提供了ThreadLocal从另外一个角度解决这个问题。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程访问同一数据的冲突,ThreadLocal提供线程安全的对象封装,编写代码时可以把不安全的变量封装到ThreadLocal中。
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,这样ThreadLocal就为每一个线程维护变量的副本。ThreadLocal类支持泛型,其接口很简单,只有4个方法:
1.void set(T value)设置当前线程的线程局部变量的值。
2.public T get()该方法返回当前线程所对应的线程局部变量。
3.public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应4.该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
5.protected T initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(T)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
下面给一个多个线程操作同一数据的例子:
package test;
public class ThreadLocalTest {
public static void main(String args[]){
SeqNum sn = new SeqNum();
Client t1 = new Client(sn);
Client t2 = new Client(sn);
Client t3 = new Client(sn);
t1.start();
t2.start();
t3.start();
}
}
// 封装要操作的数字变量
class SeqNum{
private static ThreadLocal<Integer> num=new ThreadLocal<Integer>()
{
// 通过匿名内部类覆盖ThreadLocal的initValue方法
public Integer initialValue(){
return 0;
}
};
public int getNext(){
num.set(num.get()+1);
return num.get();
}
}
class Client extends Thread{
private SeqNum sn;
public Client(SeqNum sn) {
this.sn = sn;
}
public void run(){
for(int i=0;i<3;i++){
System.out.println("threa"+Thread.currentThread().getName()+" sn"+sn.getNext());
}
}
}
输出为:
threaThread-0 sn1
threaThread-0 sn2
threaThread-0 sn3
threaThread-2 sn1
threaThread-1 sn1
threaThread-2 sn2
threaThread-2 sn3
threaThread-1 sn2
threaThread-1 sn3
课件各自的序号独立,没有发生相互干扰的情况。
Spring使用ThreadLocal解决线程安全问题,我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,而在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。