维持线程封闭性的一种常用方法是使用ThreadLocal,这是一个相对于线程来说是全局的变量。可以理解成线程的全局变量。ThreadLocal方法提供了get和set方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当前执行线程在调用set时设置的最新值。
ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个数据库连接对象。从而避免在调用每个方法时,都需要传递一个Connction对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不一定是线程安全的了。通过将jdbc的连接保存到ThreadLocal对象中,每个线程都会有一个自己的连接。
简单写了一个ThreadLocal的demo
package com.wsy.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
// 创建一个线程池
public static ExecutorService pool = Executors.newFixedThreadPool(5);
// 创建一个线程的全局变量
private static ThreadLocal<String> stringHolder = new ThreadLocal<String>();
public static void main(String[] args) {
//在线程外做的任何初始化都不会影响到线程
stringHolder.set("test");
// 启动一个线程将其赋值成test1
pool.execute(new Runnable() {
@Override
public void run() {
stringHolder.set("test1");
System.out.println(stringHolder.get());
}
});
// 再启动一个线程再次打印该值
pool.execute(new Runnable() {
@Override
public void run() {
// 预期打印的结果是test1,结果打印了null,原因就是ThreadLocal是线程相关的
System.out.println(stringHolder.get());
}
});
}
}
打印结果:
可以看到第二个线程打印的是null,并不是test,也不是test1,说明不论是在主线程中初始化ThreadLocal还是在其他线程中初始化ThreadLocal,都不会对某个线程的ThreadLocal对象的值造成影响。
这时候有人会问如果我想统一地做一个初始化,使得所有线程都可以使用怎么办?java当然考虑到了这一点,这一点可能是设计的源头之一。代码如下:
package com.wsy.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
// 创建一个线程池
public static ExecutorService pool = Executors.newFixedThreadPool(5);
// 创建一个线程的全局变量
private static ThreadLocal<String> stringHolder = new ThreadLocal<String>(){
@Override
protected String initialValue(){
return "test5";
}
};
public static void main(String[] args) {
//在线程外做的任何初始化都不会影响到线程
stringHolder.set("test");
// 启动一个线程将其赋值成test1
pool.execute(new Runnable() {
@Override
public void run() {
stringHolder.set("test1");
System.out.println(stringHolder.get());
}
});
// 再启动一个线程再次打印该值
pool.execute(new Runnable() {
@Override
public void run() {
// 预期打印的结果是test1,结果打印了null,原因就是ThreadLocal是线程相关的
System.out.println(stringHolder.get());
}
});
}
}
打印结果如下:
可以看到此时打印出来的就是test5,我们可以通过重写initialValue方法来达到一次初始化。各个线程都有一个副本的情况。所谓师父领进门,修行在个人。知道了ThreadLocal的用法,具体什么时候使用,怎么用就是看个人的悟性了~