Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Javaスレッドプログラミングの基礎

 Javaスレッドプログラミングの基礎

スレッドプログラミングのハマりどころを中心にどうするとよいのかをまとめたスライド。

かとじゅん

May 11, 2018
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. 「半導体の集積密度は18 ~24 カ月で倍増し、チップは処理能力 が倍になってもさらに小型化が進むという法則( 経験則) 」。 1965 年に、インテル社創設者の1 人であるゴードンムーアが提 唱

    つまり、1 年半から2 年でIC チップに集積されるトランジスタ 数が倍増していく( プロセスの微細化とも呼ばれる) ということ 微細化が滞るとコンピューティングの進化も止まってしま う、と考えられていた 3
  2. ムーアの法則を維持する戦略 クロック周波数をあげる トランジスタの集積度をあげ、1 秒間で実行できる命令数を増 やす戦略。クロックとはCPU の動作基準となる時間の単位。 3GHz は1 秒間で30 億回のクロックを表す

    ハードウェアを改良すればソフトウェアのパフォーマンスも 改善された時代 CPU に搭載するコアを複数にする クロック数アップ戦略は発熱やエネルギー効率の問題が障害 となり、現在では複数のタスクをそれぞれのコアが並列 (Parallel) に処理できる、マルチコアが一般的になっている 4
  3. FYI: 並行処理と並列処理 並行(Concurrent) は、「複数の動作が、論理的に、順不同もし くは同時に起こりうる」こと 1CPU で複数の仕事を並行処理するには、処理時間を非常に短 い時間単位で分割する、タイムスライス( タイムクォンタム) が

    利用される。ミクロな観点では同時ではない 並列(Parallel) は、「複数の動作が、物理的に同時に起こるこ と」 - 目的は計算速度の向上。スレッドをコアに割り当てて、並列 にタスクを処理させる必要がある ※本スライドでは、この二つの概念を特に区別しない場合は、 並 行 を一般的な概念を示す用語として利用する 5
  4. タスクの要求毎にスレッドを生成する例 class CommandExecutor { def execute(cmd: () => Unit): Unit

    = { /* タスクが大量に同時発生した場合、スレッドも大量に生成され システムリソースがいたずらに消費されてしまう */ new Thread(() => cmd()).start() } } OS ごとに作成できるスレッド数の上限は決まっている。その上 限を超えるとOutOfMemoryError 生成後は、アクティブかどうかに関わらずメモリを消費する 9
  5. ExecutorService( スレッドプール) を利用す る class CommandExecutor(nThreads: Int) { // 固定長のスレッドプールを持つExecutorService

    private val pool: ExecutorService = Executors .newFixedThreadPool(nThreads) def execute(cmd: () => Unit): Future[_] = { // タスクをスレッドプールにサブミットする pool.submit(() => cmd()) } } 10
  6. FYI: 計算結果を返すCallable case class Result(in: Long, out: BigDecimal) class Fac(i:

    Long) extends Callable[Result] { private def fac(n: Long): BigDecimal = (1L to n).foldLeft(BigDecimal(1L))(_ * _) override def call(): Result = Result(i, fac(i)) } 計算結果を返す場合はRunnable ではなくFuture(Java) を使う 13
  7. val executor = Executors.newFixedThreadPool(3) val futures = ArrayBuffer[Future[Result]]() for (i

    <- 2L to 50L) { val future: Future[Result] = executor.submit(new Fac(i)) futures.append(future) } futures.foreach { future => try { val result = future.get() // 値が取得できるまでブロックする println(f"fac(${result.in}%d) = ${result.out}%s") } catch { case ex: Throwable => ex.printStackTrace() } } executor.shutdown() executor.awaitTermination(5, TimeUnit.SECONDS) 14
  8. メモリモデル: 可視性の問題 スレッドt は停止しません。なぜかわかりますか?(HotSpot Server VM) private var stopRequested =

    false val t = new Thread(() => { println("thread: start") var i = 0 while (!stopRequested) { i += 1 } println("thread: finish") }) t.start() TimeUnit.SECONDS.sleep(1) println("stop request") stopRequested = true 17
  9. 以下のコードに変換されてしまう( 巻き上げ最適化) if (!stopRequested) while(true) { i += 1 }

    Java メモリモデルでは、スレッドごとに最適化するため、値の 変更が他のスレッドへ即座に伝わらない可能性がある デフォルトではスレッドからの書き込みが、別のスレッドが読 み込める保証( 可視性) がない。@volatile を使えば、異なるスレ ッドからも読めるようになる @volatile private var stopRequested = false // 巻き上げ最適化はされない 18
  10. FYI: volatile の有効範囲 case class Persion(name: String) @volatile val person

    = Person("KATO") @volatile val persons = Array(Person("KATO")) // 指定された参照以外は可視性は保証されません val name = person.name val p = persons(0) 19
  11. 固有ロック(synchronized) を使う方法 private var stopRequested = false def requestStop(): Unit

    = synchronized { stopRequested = true } def isStopRequested(): Boolean = synchronized { stopRequested } val t = new Thread(() => { println("thread: start") var i = 0 while (!isStopRequested) { i += 1 } println("thread: finish") }) t.start() TimeUnit.SECONDS.sleep(1) println("stop request") requestStop() 20
  12. ブロッキング中にスレッドを停止する例 フィボナッチ数列をブロッキングキューに追加するタスクを停止 させる object Main extends App { val task

    = new FibGenerator() val thread = new Thread(task) println("thread start") thread.start() TimeUnit.SECONDS.sleep(1) println("cancel start") task.cancel() thread.join() println("cancel finish") println(s"result = ${task.fibonaccis().toList}") } 22
  13. // フィボナッチ数列をブロッキングキューに追加するタスク class FibGenerator extends Runnable { @volatile private var

    cancelled = false @volatile private var thread: Thread = _ private val queue = new ArrayBlockingQueue[BigInt](2) def fibonaccis(): Iterable[BigInt] = queue.asScala // ... 23
  14. ブロック中はフラグを変えても終了できない。この例ではタスク を実行するスレッドに対して、インタラプションを発生させてタ スクを中断させている。 override def run(): Unit = { thread

    = Thread.currentThread() var n = BigInt(1) try { while (!cancelled) { val f = fibonacci(n) println(f"fib($n) = $f") queue.put(f) // キューがいっぱいになるとブロックする n = n + BigInt(1) } } catch { case _: InterruptedException => println(" 割り込みが発生しました") } } 24
  15. def cancel(): Unit = { require(thread != null) cancelled =

    true thread.interrupt() // インタラプションを発生 } private def fibonacci(n: BigInt): BigInt = { if (n == BigInt(0) || n == BigInt(1)) n else { val a = n + BigInt(-2) val b = n + BigInt(-1) fibonacci(a) + fibonacci(b) } } } BlockingQueue#put はインタラプション発生時にブロックを解 除してくれるが、実装依存。一般的には実装されるべき。 25
  16. 競り合い状態 class Sequence(private var value: Int = 0) { def

    getAndIncrement(): Int = { value += 1 value } def get(): Int = value } // 一つのSequence を複数のスレッドから操作すると? class SequenceTask(sequence: Sequence) { // ... override def run() = { val threadId = Thread.currentThread().getId() while(!isTerminated) { val count = sequence.getAndIncrement() println(f"$threadId%04d:$count%05d") } } } 27
  17. 28

  18. += は 以下の3 つの操作となる。 値を読む それに1 を加える 値を更新する リード・モディファイ・ライト操作 これらの操作は、それぞれが独立していて、直前の状態に依

    存している += , -= 以外でもありうる チェック・ゼン・アクト操作も競り合い状態になりやすい 状態をチェックし、その結果に基づいて、何らかのアクショ ンを行う操作 29
  19. class Sequence(private var value: Int = 0) { def getAndIncrement():

    Int = synchronized { value += 1 value } def get(): Int = synchronized { value } // getAndIncrement 後のvalue を可視とするためsynchronized を適用 // そうしない場合は不要 } 30
  20. 命令の順序替えについて 最適化により、a = 1 よりx = b が先に実行される可能性がある ( 同じ結果を導出できるセマンティクスであれば、計算プロセス

    を最適化してもよいという前提) a = 1 x = b この最適化を知らないと並行プログラミングでは問題になること がある。 31
  21. var x: Int = 0; var y: Int = 0;

    var a: Int = 0; var b: Int = 0 def threadTest(testNo: Int) = { val startLatch = new CountDownLatch(1) x = 0; y = 0; a = 0; b = 0 val ta = new Thread({ () => startLatch.await() a = 1 x = b }) val tb = new Thread({ () => startLatch.await() b = 1 y = a }) ta.start(); tb.start() startLatch.countDown() ta.join(); tb.join() if (x == 0 && y == 0) { // 思い込みに反して (x, y) = (0, 0) が存在する println(s"t = $testNo, x = $x, y = $y") } } 32