ThreadLocal作用和原理
我们知道Java多线程会出现安全问题主要原因是因为多线程同时访问一个共享数据,从而我们解决多线程问题的思路主要有2个:
1.给共享数据加锁
2.避免多线程操作同一共享数据
而思路1是我们平时比较常用的一种方式,但是既然是加锁就必然会有一些性能方面的问题,比如线程等待。
所以今天我们讲讲思路2,但是思路2并不能适用于所有线程安全问题,因为在很多具体业务场景下必须让多线程访问同一数据,所以思路2适用于可以将共享数据变为线程私有变量的场景,例如Android中Handler的实现中。
Android中的Handler中实现线程–Looper一对一关系使用了ThreadLocal。
首先讲一下ThreadLocal的原理,ThreadLocal是一个通过空间换时间的多线程并发问题的解决工具,它给每个线程提供了一个变量副本,实现了共享变量在多个线程间的隔离,比起synchronized通过加锁实现线程安全 ThreadLocal 的效率更高是一种无锁编程的实现。
首先我们以一个例子为切入点来看ThreadLocal的内部原理:
public class ThreadLocalTest {
private static ExecutorService service = Executors.newCachedThreadPool();
public static void main(String[] args) {
ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
threadLocal.set(Boolean.TRUE);
for (int i=0;i<100;i++){
service.execute(() -> {
threadLocal.set(Boolean.FALSE);
System.out.println("子线程设置为:" + threadLocal.get());
});
}
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程设置为:" + threadLocal.get());
service.shutdown();
}
}
我们可以看到输出为:
子线程设置为:false
...
子线程设置为:false
主线程设置为:true
我们在主线程中将threadLocal的值设置为true,然后开了一百个子线程来将它的值改为false,为了更好的复现线程不安全的情况,我们特地还给主线程休眠了3S,这时候到最后输出的主线程的threadLocal的值依然是true。这就说明了,子线程中的设置并没有影响到主线程的值,其实各个线程中操作的都是各自线程对应的threadLocal的值。
如果我们将以上代码中ThreadLocal类型改为AtomicBoolean那么最终的输出结果将会是
子线程设置为:false
...
子线程设置为:false
主线程设置为:false
其实ThreadLocal就是这么回事,就等于我们在每个线程中都创建了一个布尔值。
我们假设有一个场景,每个线程都会做各自线程相关的操作,最后要将该线程里的操作写入每个线程中id作为名称的文件中。
public class ThreadLocalTest {
private static ExecutorService service = Executors.newCachedThreadPool();
public void main() {
ThreadLocal<Integer> taskId = new ThreadLocal<>();
taskId.set(1);
servic