文章目录
ThreadLocal与同步机制都能保证线程安全,但是其使用的方法是不同的,ThreadLocal采用多线程多副本,不同线程之间互不影响。同步机制保证同一时候只能有一个线程访问共享资源。
也就是说ThreadLocal只能被同一个线程访问。不同线程即使是访问通一个ThreadLocal变量,线程之间的ThreadLocal变量也是不可见的
常见使用场景
简单例子
public class GreySwitchContext implements AutoCloseable{
// 创建一个泛型的ThreadLocal
public static final ThreadLocal<Boolean> ctx = new ThreadLocal<>();
public static final boolean getGreySwitch() {
// 获取ThreadLocal
return ctx.get();
}
public static final void setGreySwitch(boolean greySwitch) {
// 设置ThreadLocal
ctx.set(greySwitch);
}
@Override
public void close() throws Exception {
// 清除ThreadLocal 的值
ctx.remove();
}
}
初始化值
指定初始值的几种方法:
- 重写 initialValue
- 提供一个Suppplier实现
- 延迟设置
重写initial方法
最简单的方法就是创建一个ThreadLocal的匿名子类,然后重写他的initialValue方法
public static final ThreadLocal<Boolean> ctx = new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
return Boolean.TRUE;
}
};
注意:如果默认值是一个随机数的话,每个线程看到的初始值是不一样的,适用于某些场景,比如初始值是当前时间。
提供一个Supplier 实现
第二个实现初始化值的方法是用它的静态工厂方法 withInitial(Supplier) , 传一个Supplier的接口实现作为参数。
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
return String.valueOf(System.currentTimeMillis());
}
});
Supplier是一个函数接口,所有可以用Lambda表达式
ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(
()->{return String.valueOf(System.currentTimeMillis());}
);
看起来比之前的更短一些了,其实还可以更短
ThreadLocal<String> threadLocal2 = ThreadLocal.withInitial(
()-> String.valueOf(System.currentTimeMillis()));
延迟设置
某些场景初始化值可能会依赖其他配置信息,导致你在创建ThreadLocal的时候无法给默认值,这种场景下就要用到延迟设置了下面是一个例子:
public class MyDateFormatter {
private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();
public String format(Date date) {
SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
return simpleDateFormat.format(date);
}
private SimpleDateFormat getThreadLocalSimpleDateFormat() {
SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
if(simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormatThreadLocal.set(simpleDateFormat);
}
return simpleDateFormat;
}
}
上面这个例子,我们看到format方法调用getThreadLocalSimpleDateFormat方法是怎么获取一个SimpleDateFormat对象的。如果ThreadLocal里的SimpleDateFormat 是空的话就创建一个。一个线程就使用一个SimpleDateFormat对象。我们知道 SimpleDateFormat这个类是线程不安全的,所以多线程是不能使用它的,利用ThreadLocal完美解决这个问题。
线程池或者ExecutorService中使用ThreadLocal
如果你打算在ExecutorService或者线程池中使用ThreadLocal那么是不保证哪个线程会执行到哪个任务的,是随机的。所以你想每个线程都有他自己的对象,那么用ThreadLocal是very nice的。
最后再来一个例子
public class ThreadLocalExample {
public static void main(String[] args) {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}
public class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Override
public void run() {
threadLocal.set( (int) (Math.random() * 100D) );
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(threadLocal.get());
}
}
创建了同一个MyRunnable对象sharedRunnableInstance 然后传给两个不同的线程。如果这里不是threadLocal,且set方法是synchronized修饰的,那么第二个线程的值会把第一个线程的值覆盖
但是threadLocal使两个线程之间的值不可见所以不会被覆盖。
InheritableThreadLocal
ThreadLocal的一个子类