并发编程之 ThreadLocal( 二 )

但是加 synchronized 这种方式虽然也能保证线程安全,但是这种方式效率会比较低,毕竟同一时刻下,只能有一个线程能够执行程序,这显然不是最好的方案,下面我们来了解下更高效的方式,就是利用 ThreadLocal 类来实现 。
ThreadLocal介绍:每个线程需要一个独享的对象,每个 Thread内有自己的实例副本,这些实例副本是不共享的,让某个需要用到的对象在线程间隔离,即每个线程都有自己的独立的对象 。
使用ThreadLocal 的好处

  • 达到线程安全
  • 不需要加锁,提高执行效率
  • 合理利用内存,节省开销
以下代码,我们构建了一个内部类 ThreadSafeFormatter 类,在类内部定义 ThreadLocal 的成员变量,并重写了 initialValue 方法,返回的参数就是 new 出来的 SimpleDateFormat 对象 。
public class ThreadLocalTest {public static ExecutorService threadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) {for (int i = 0; i < 1000; i++) {int finalI = i;threadPool.submit(() -> System.out.println(new ThreadLocalTest().sec2Date(finalI)));}}private String sec2Date(int seconds) {// 在 ThreadLocal 第一个 get 的时候把对象初始化出来,对象的初始化时机可以由我们控制SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();return dateFormat.format(seconds * 1000);}static class ThreadSafeFormatter {// 方式一(原始方式)public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {// 初始化@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");}};// 方式二(Lambda表达式)public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));}}【并发编程之 ThreadLocal】输出结果:
并发编程之 ThreadLocal

文章插图
结果中我们可以看出,没有输出重复的时间值(可以多运行几次观察下),因此我们通过 ThreadLocal 这种方式就达到了线程安全,并且还节省了系统的开销,合理利用了内存 。
由此我们可以得到一个结论:每个线程的 SimpleDateFormat 是独立的,一共有 10 个 。每个线程会平均执行 100 个任务,每个线程之间都是复用一个 SimpleDateFormat 对象 。
ThreadLocal 源码分析在了解 ThreadLocal 源码之前,我们先了解以下 Thread,ThreadLocalMap 以及 ThreadLocal三者之间的关系 。
首先,我们创建的每一个 Thread 对象中都持有一个 ThreadLocalMap 成员变量,而 ThreadLocalMap 中可以存放着很多的 key 为 ThreadLocal 的键值对 。
并发编程之 ThreadLocal

文章插图

并发编程之 ThreadLocal

文章插图
主要方法介绍
  • T initialValue() : 初始化,返回当前线程对应的“初始值”,这是一个延迟加载的方法,只有在调用get的时候,才会触发 。
  • void set(T t) : 为这个线程设置一个新值 。
  • T get() : 得到这个线程对应的value 。如果是首次调用 get(),则会调用 initialize 来得到这个值 。
  • void remove() :删除对应这个线程的值 。
initialValueSimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
在上述代码,我们并没有显式地调用这个 initialValue 方法,而是调用了 get 方法,而在 get 方法中,它会去调用
setInitialValue 方法,在 该方法内部它才会去调用我们重写的 initialValue 方法 。
并发编程之 ThreadLocal

文章插图

经验总结扩展阅读