解释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等 )

results matching ""

    No results matching ""