ThreadLocal 是一个线程的本地变量,也就意味着这个变量是线程独有的,是不能与其他线程共享的,这样就可以避免资源竞争带来的多线程的问题,这种解决多线程的安全问题和lock(这里的lock 指通过synchronized 或者Lock 等实现的锁) 是有本质的区别的:
- lock 的资源是多个线程共享的,所以访问的时候需要加锁。
- ThreadLocal 是每个线程都有一个副本,是不需要加锁的。
- lock 是通过时间换空间的做法。
- ThreadLocal 是典型的通过空间换时间的做法。
当然他们的使用场景也是不同的,关键看你的资源是需要多线程之间共享的还是单线程内部共享的。
ThreadLocal 的使用是非常简单的,看下面的代码:
public class Test {
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
//设置值
local.set("hello word");
//获取刚刚设置的值
System.out.println(local.get());
}
}
ThreadLocal的数据结构
为什么ThreadLocalMap 采用开放地址法来解决哈希冲突?
ThreadLocal 往往存放的数据量不会特别大,这个时候开放地址法简单的结构会显得更省空间(链地址法需要额外的指针空间)
常用的hash解决方法有:拉链法(HashMap,指针需要占用空间
)、开发地址(如果发生冲突,那就基于冲突位置再次探测寻址,直至不冲突,适用于记录总数可以预知的场景,如果位桶不够用就得扩容,扩容影响性能
)、再hash(如果第1个hash函数冲突,那就使用第2个,多次hash是有时间成本的
)
ThreadLocal应用场景
传递参数
保证线程安全
SimpleDateFormat是线程不安全的,例如下面的写法会报错:
日期转换的一个工具类
public class DateUtil {
private static final SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String dateStr) {
Date date = null;
try {
date = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
然后将这个工具类用在多线程环境下
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
service.execute(()->{
System.out.println(DateUtil.parse("2019-06-01 16:34:30"));
});
}
service.shutdown();
}
结果报异常了:
Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-12" Exception in thread "pool-1-thread-7" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-11" Exception in thread "pool-1-thread-5" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:601)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Tue Jun 01 16:34:30 CST 1
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Tue Jun 01 16:34:30 CST 1
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
Sat Jun 01 16:34:30 CST 2019
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: "2019."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at Test.parse(Test.java:15)
at Test.lambda$main$0(Test.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
解决方案4:用ThreadLocal,一个线程一个SimpleDateFormat对象
public class DateUtil {
private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(
()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date parse(String dateStr) {
Date date = null;
try {
date = threadLocal.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
配合线程池使用,既保证了线程安全,又不至于像 解决方案1 创建太多的SimpleDateFormat对象。
ThreadLocal 保证的是单个线程内部访问的是同一个实例,不同线程访问的不是同一个实例。详见:ThreadLocal线程单例
评论