Spring ThreadLocal 基础知识

Spring通过各种模板类降低了开发者使用各种数据持久技术的难度,这些模板类都是线程安全的。但是这些资源本身却是非线程安全的。根据传统的经验,如果某个对象是非线程安全的话,在多线程的环境下,对于对象的访问都必须采用同步机制,但是模板类并没有采用同步机制,因为线程的同步会降低并发性,Spring的模板类就是采用ThreadLocal 解决了在多线程环境下,不采用同步的方式解决了多线程的难题。

JDK1.2中提供了java.lang.ThreadLocal,他不是一个线程,而是线程的一个本地化对象。当工作于多线程环境中的对象使用ThreadLocad进行维护的时候,ThreadLocad会为每一个使用这个变量的线程分配一个独立的变量副本。所以每一个线程可以独立的改变自己的副本。而不会影响其他线程所对应的副本。从线程的角度来看,就如同使用局部变量一样。

ThreadLocal的接口很简单,只有4个方法:

void set(Object value)
public Object get()
public void remove()
protected object initialValue()

注意:从jdk5开始,支持泛型。

**ThreadLocal为了为每一个线程维护一个独立的变量副本,实现思路是在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,key是线程对象,value是对应的变量副本。**自己写一个比较幼稚的版本吧:

class MyThreadLocal {
private Map map = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
map.put(Thread.currentThread(), newValue);
}
public Object get() {
Thread currentThread = Thread.currentThread();
Object obj = map.get(currentThread);
if (null == obj && !map.containsKey(currentThread)) {
obj = initialValue();
map.put(currentThread, obj);
}
return obj;
}
public void remove() {
map.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}

有兴趣的朋友可以看看jdk中的ThreadLocal是如何实现的。

下面我们用一个实例来结束这篇文章:

public class SequenceNumber {
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }
    private static class TestClient extends Thread {
        private SequenceNumber sequenceNumber;
        public TestClient(SequenceNumber sequenceNumber) {
            this.sequenceNumber = sequenceNumber;
        }
        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println("thread\[" + Thread.currentThread().getName() + "\]sequenceNumber\[" + sequenceNumber.getNextNum() + "\]");
            }
        }
    }
    public static void main(String\[\] args) {
        SequenceNumber sequenceNumber = new SequenceNumber();
        new TestClient(sequenceNumber).start();
        new TestClient(sequenceNumber).start();
        new TestClient(sequenceNumber).start();
    }
}

运行结果:

thread\[Thread-2\]sequenceNumber\[1\]
thread\[Thread-1\]sequenceNumber\[1\]
thread\[Thread-0\]sequenceNumber\[1\]
thread\[Thread-1\]sequenceNumber\[2\]
thread\[Thread-2\]sequenceNumber\[2\]
thread\[Thread-1\]sequenceNumber\[3\]
thread\[Thread-0\]sequenceNumber\[2\]
thread\[Thread-2\]sequenceNumber\[3\]
thread\[Thread-0\]sequenceNumber\[3\]

可以看出虽然每个线程都共享一个sequenceNumber,但是却没有互相干扰,而是打印出自己独立的序列号。因此很好的证明了上面的结论:

ThreadLocal为了为每一个线程维护一个独立的变量副本,实现思路是在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,key是线程对象,value是对应的变量副本。