解释ThreadLocal
答案
相对于synchronized 对临界资源加锁,ThreadLocal提供了另一种不同的思路:避免临界资源的出现,让每个线程拥有自己独自占有的资源,避免了争用。
分析
例如: 多个线程想使用SimpleDateFormat 来进行日期的格式转换
P.S.
JDK 8 之后可以使用 DateTimeFormatter 代替 SimpleDateFormat,它是线程安全的
问题代码
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()
+ " " + sdf.parse("1951-04-21"));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// 在运行时会大量出现 java.lang.NumberFormatException ...
}
解决思路A
SimpleDateFormat不是线程安全的,多个线程使用它时,必须加锁排队:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (sdf) {
try {
System.out.println(Thread.currentThread().getName()
+ " " + sdf.parse("1951-04-21"));
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
缺点
效率低,并行操作变为了串行
解决思路B
其实可以让每个线程拥有自己的SimpleDateFormat 对象,而不是大家争用一个:
// 初始化每个线程自己的SimpleDateFormat对象
ThreadLocal<SimpleDateFormat> threadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 获取自己的SimpleDateFormat对象
SimpleDateFormat sdf = threadLocal.get();
try {
System.out.println(Thread.currentThread().getName()
+ " " + sdf.parse("1951-04-21"));
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
注意
- 没有了锁,能并行,效率高
- 实际使用时,ThreadLocal对象常被作为静态变量
- 开源框架(如spring)中大量使用了ThreadLocal来保证诸如Connection等对象的线程安全
- ThreadLocal还有一个额外的好处:在一个线程的内多次方法调用之间,保证它们使用的是同一个资源对象(例如Connection 或 hibernate Session等 )