java中的java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。通俗一点,就是在每个Thread中,都有一个ThreadLocalMap集合,在该map中,key为该ThreadLocal对象,value即为ThreadLocal中变量的副本,所以ThreadLocal只是操作每个线程的ThreadLocalMap而已。
ThreadLocal有三个主要方法:
public T get() { }
public void set(T value) { }
public void remove() { }
get方法的实现:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在get方法中,首先得到当前线程实例中保存的ThreadLocalMap,如果存在该Map,则从Map中获取key为this(当前ThreadLocal)对应的值;如果不存在该Map或Value不存在,则返回初始值。
set方法实现如下:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
和get方法同理,先得到ThreadLocalMap,如果Map存在,则将键值对(键为this,即当前ThreadLocal)保存在该Map中;如果ThreadLocalMap不存在,则创建该Map,并保存键值对。
remove方法实现如下:
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
接下来我们结合一个例子,来加深对ThreadLocal的理解。
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> localInt = new ThreadLocal<Integer>();
private static ExecutorService executorService;
static {
executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
private AtomicInteger threadCount = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("Thread-" + threadCount.incrementAndGet());
return thread;
}
});
}
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(0);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
ai.incrementAndGet();
//每个线程有各自的变量副本,不会受其他线程影响。
localInt.set(ai.get());
//改变自己的副本值,不会影响其他线程的ThreadLocal。
if(ai.get() == 1) {
localInt.set(9999);
}
System.out.println(Thread.currentThread().getName() + " " + localInt.get());
}
});
}
executorService.shutdown();
}
}
运行结果如下:
Thread-2 9999
Thread-1 2
Thread-6 4
Thread-4 3
Thread-3 5
Thread-8 6
Thread-10 7
Thread-5 8
Thread-7 9
Thread-9 10
我们发现其中一个线程将本地的ThreadLocal变量副本设置为9999,并没有对其他线程产生任何影响。
结论:
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
灵活使用ThreadLocal,可以解决多线程环境下的线程安全问题。如在Spring中,使用了ThreadLocal解决了单例的线程安全问题;在MyBatis中,使用ThreadLocal实现了线程安全的SqlSessionManager。需要记住的是:ThreadLocal不是解决多线程环境下共享变量的访问问题的,而是为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。