ThreadLocal线程安全吗,先说结论:ThreadLocal线程安全。说实话我之前虽然觉得是,但是也不确定,但是今天通过实验证明是线程安全的。
@RestController
public class ThreadLocalController {
private final ThreadLocal<String> threadLocal = new ThreadLocal<>();
@RequestMapping("/test")
public String test(@RequestParam String value){
String s = threadLocal.get();
System.out.println(Thread.currentThread().getName()+"___"+s);
if (s==null){
threadLocal.set(value);
s=threadLocal.get();
}
try {
//为了避免线程执行完值被移除的可能,睡眠10秒,确保执行下一次请求时,上一个线程还是活的
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return s;
}
}
实验如下:
短时间内发起3次请求,确保发起请求时,前面的线程还没执行完。请求示例如下:
http://localhost:8090/test?value=zhangsan1
http://localhost:8090/test?value=zhangsan2
http://localhost:8090/test?value=zhangsan3
实验结果:
控制台:
http-nio-8090-exec-4___null
http-nio-8090-exec-5___null
http-nio-8090-exec-6___null
浏览器:
zhangsan1
zhangsan2
zhangsan3
实验结论:
ThreadLocal线程安全。
实验方式二:
private static ThreadLocal threadLocal= new ThreadLocal();
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1000));
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int j = i;
executor.execute(()->{
Object o = threadLocal.get();
if (o==null){
threadLocal.set(j);
o = threadLocal.get();
}
System.out.println("o = " + o);
});
}
}
实验结果如下:
o = 6
o = 9
o = 4
o = 3
o = 0
o = 5
o = 2
o = 7
o = 8
o = 1
实验结论:
ThreadLocal线程安全。
但是实验方式二有点小问题,比如循环次数改为20
private static ThreadLocal threadLocal= new ThreadLocal();
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1000));
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
int j = i;
executor.execute(()->{
Object o = threadLocal.get();
if (o==null){
threadLocal.set(j);
o = threadLocal.get();
}
System.out.println("o = " + o);
});
}
}
实验结果如下:
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 1
o = 0
o = 5
o = 6
o = 2
o = 8
o = 3
o = 7
o = 9
o = 4
出现了重复的情况,这是因为线程池的线程是重复利用的,解决方法如下:
private static ThreadLocal threadLocal= new ThreadLocal();
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1000));
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
int j = i;
executor.execute(()->{
try {
Object o = threadLocal.get();
if (o==null){
threadLocal.set(j);
o = threadLocal.get();
}
System.out.println("o = " + o);
} finally {
//每次执行完把ThreadLocal里的值移除
threadLocal.remove();
}
});
}
}
为什么同样是线程池,前面不会重复呢?
因为线程池的线程小于核心线程数的时候,每次使用线程池执行任务都会创建一个核心线程,即使这时候有些核心线程是空闲状态,直至线程池的线程数等于核心线程数。所以前面循环10次,核心线程数也为10,刚好就没次执行都会创建一个新的线程而不会重复。
所以线程池使用ThreadLocal时必须确保每次用完都要移除掉ThreadLocal的值。