如何在scala中使用ThreadLocal?

时间:2018-02-20 11:39:04

标签: scala thread-safety

我目前使用此代码从PDF文件中提取文本:

String text = new PDFTextStripper().getText(PDDocument.load(content));

我需要在多线程应用程序中运行它,PDFTextStripper不是线程安全的。我想在每个帖子中只使用ThreadLocal初始化PDFTextStripper一次。我尝试如下:

import ammonite.ops._
import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.text.PDFTextStripper

(0 until 100).par.foreach { i =>
  println(s"#$i. START.")
  val pdfTextStripper = new java.lang.ThreadLocal[PDFTextStripper] {
    override def initialValue: PDFTextStripper = {
      println(s"new PDFTextStripper. #$i")
      new PDFTextStripper
    }
  }

  val content = read.bytes! Path(FilePath(s"/data/file_$i.pdf"), pwd)
  val doc = PDDocument.load(content)
  println(pdfTextStripper.get.getText(doc))
}

我有100个文档,我正在使用(0 until 100).par,它正在使用10个线程,我想。因此,文本new PDFTextStripper. #$i应该只显示10次,但它会出现100次。 PDFTextStripper未被重复使用。为什么呢?

1 个答案:

答案 0 :(得分:2)

您应该只将ThreadLocal实例化一次,然后在并行计算中使用它。每个调用get()的线程都会创建一个PDFTextStripper,然后重复使用它:

val pdfTextStripper = new java.lang.ThreadLocal[PDFTextStripper] {
  override def initialValue: PDFTextStripper = {
    println(s"new PDFTextStripper") // this will be printed once per thread
    new PDFTextStripper
  }
}

(0 until 100).par.foreach { i =>
  println(s"#$i. START.")
  val content = read.bytes! Path(FilePath(s"/data/file_$i.pdf"), pwd)
  val doc = PDDocument.load(content)
  println(pdfTextStripper.get.getText(doc))
}

一些直觉:将ThreadLocal个实例视为 maps 是有用的,其中键是线程,值是由initialValue获得的延迟评估对象。所以 - 您只需要一个这样的地图,您不希望在每次迭代时重新创建地图(从而失去对前一个地图的访问权限)。