关于ThreadLocal的一些反思

ThreadLocal 是一个用于提供线程局部变量的工具类,它主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。例如Spring 中的单例Bean在多线程调用的时候,并行计算的时候各个线程缓存自己的中间计算结果等场景。

ThreadLocal 是解决线程安全问题一个很好的思路,它通过为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。在很多情况下,ThreadLocal 比直接使用 synchronized 同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

class PrintRunnable extends Runnable {
  val number = new ThreadLocal[Double]

  override def run(): Unit = {
    number.set(Math.random())
    println(number.get())
  }
}

object SimpleApp {
  def main(args: Array[String]): Unit = {
    val printRunnable = new PrintRunnable

    val thread1 = new Thread(printRunnable)
    val thread2 = new Thread(printRunnable)

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()
  }
}
0.5157676349493098
0.37557496403907353

但是在父子线程要传递一些变量的时候会发现数据不能共享,需要 InheritableThreadLocal 来实现父子线程变量的传递。

class PrintRunnable extends Runnable {
  val number = new ThreadLocal[Double]

  override def run(): Unit = {
    number.set(Math.random())
    println(number.get())
    
    val childThread = new Thread(new Runnable {
      override def run(): Unit = {
        println(number.get())
      }
    })
    childThread.start()
    childThread.join()
  }
}

object SimpleApp {
  def main(args: Array[String]): Unit = {
    val printRunnable = new PrintRunnable

    val thread1 = new Thread(printRunnable)
    val thread2 = new Thread(printRunnable)

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()
  }
}
0.5475226099407153
0.8376546404552231
null
null

正确的做法:

class PrintRunnable extends Runnable {
  val number = new InheritableThreadLocal[Double]

  override def run(): Unit = {
    number.set(Math.random())
    println(number.get())

    val childThread = new Thread(new Runnable {
      override def run(): Unit = {
        println(number.get())
      }
    })
    childThread.start()
    childThread.join()
  }
}

object SimpleApp {
  def main(args: Array[String]): Unit = {
    val printRunnable = new PrintRunnable

    val thread1 = new Thread(printRunnable)
    val thread2 = new Thread(printRunnable)

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()
  }
}
0.006425375134899158
0.021932306310074368
0.006425375134899158
0.021932306310074368

在子线程中不能修改父线程的变量:

class PrintRunnable extends Runnable {
  val number = new InheritableThreadLocal[Double]

  override def run(): Unit = {
    number.set(Math.random())
    println(number.get())

    val childThread = new Thread(new Runnable {
      override def run(): Unit = {
        println(number.get())
        number.set(0.1)
      }
    })
    childThread.start()
    childThread.join()
    println(number.get())
  }
}

object SimpleApp {
  def main(args: Array[String]): Unit = {
    val printRunnable = new PrintRunnable
    val thread1 = new Thread(printRunnable)
    thread1.start()
    thread1.join()
  }
}
0.7413853012849937
0.7413853012849937
0.7413853012849937

但是现实开发中我们更多的是使用线程池管理线程,线程复用怎么解决ThreadLocal变量的传递或者跨线程池传递。如下面场景:分布式跟踪系统,日志收集记录系统上下文,Session级Cache,应用容器或上层框架跨应用代码给下层SDK传递信息。
可以看一下这个开源项目transmittable-thread-local

发表评论

电子邮件地址不会被公开。 必填项已用*标注