javaでよく使う同期処理の罠

javaでよく使われるであろう同期処理をまとめておきたい。
使用や記述方法については、ほかの検索記事のほうが参考になるので割愛。

記事目的

javaで使う同期処理のリファレンスとして使いたい。

注意すべき点調べた際の記事をまとめておいて、後で読み直すための引き出し。

本項でわかる事

通常、同期と言うとマルチスレッド処理の並列実効性そのものを指したりする。
呼び出し元のスレッド管理で言えば、java.util.concurrent.Executorを実装したクラスを連想させる。
また、原子性可視性の問題もある。

本項ではよりピンポイントに、呼び出し先の挙動についてを取り扱う。
具体的にはsynchronized修飾子と、java.util.conccurent.lock.ReentrantLockを比較する。
なので、原子性とか可視性、呼び出し元のタスクの挙動はあまり重要視しない。

呼び出し元については、下記等も参考されたし
https://lifeinprogram.com/2018/07/01/post-70/

種類

synchronized修飾子

javaではこの修飾子によって、同期処理を言語レベルでサポートしている。
割とお手軽に、可読性が高い実装になるやつ。

ReentrantLock

java.util.conccurent.locksパッケージにあるインタフェース。
比較的リッチな機能を持っていて、実行がsynchronizedと比べてちょっと遅い(はず)

wait/notify

もはや忘れ去られた技術になりかけている奴。
今回は特に触れない

Semapho

あんまり見かけたことがないけど、こちらでも同期処理が可能。
今回は特に触れない

synchronized修飾子とReentrantLockの違い

まず大きく違うのは、機能が異なる。
修飾子に機能を求めるのはどうかと思うが、synchronized修飾子ではあまりリッチな機能はなく単に同期処理を保証するものです。

具体的には、以下の様な機能が違います。

  • 解放順序性
  • 公平性
  • 待機性
  • タイムアウト
  • 待機数確認

解放順序性

synchronized修飾子では、同期解放の順序を保証しません。
実行タスクの実行順は、任意の順で実行されます。

具体的な処理結果として、以下の様な挙動になります。

synchronized修飾子
実行想定順序 [0, 3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
実行結果順序 [0, 2, 1, 4, 5, 6, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]数字はスレッド名だと思ってください。
2スレッド目の実行時、本来であればスレッド[3]が実行される事を期待しますが[2]が
実行されます。
※稀に順序通り実行される事もあります。

Reentrant
実行想定順序 [0, 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 11, 13, 14, 15, 16, 17, 18, 19]
実行結果順序 [0, 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 11, 13, 14, 15, 16, 17, 18, 19]こちらは、synchronized修飾子とは違い、実行順序が規定されています。

公平性

ReentrantLockでは、解放待ちの待機時間が長いスレッドを優先的に処理することが可能です。
synchronized修飾子では、そのような動作は実装できません。

待機性

ReentrantLock.tryLock()により、ロック中かどうか確認できます。
ロック中だった場合の代替実装をする等、呼び出し元の実装がすこし自由になります。

タイムアウト

待機性でもあるReentrantLock.tryLock(long TimeUnit)では、タイムアウト時間を設定する事も可能です。
これにより規定値を超えてもロック開放されない場合、処理を終了するような実装が可能です。

待機数確認

ReentrantLock.getQueueLength()で現在待機されている数を確認できます。
ただし、javadocに以下記載がある通りある時点でのスナップショットの情報なので注意です。

このメソッドが内部のデータ構造をトラバースしている間にも、スレッド数が動的に変化する場合があるため、この値は推定に過ぎません。

参考リンク

公式

javadoc:ReentrantLock

個人・ポストなど

水まんじゅう2:Javaのsynchronizedは順序を保証しない。

今回の記事をまとめておく起因の一つ。

具体例としてログ出力時の順序不正について書いてあります。

A little bit of everything:ReentrantLock の lock と tryLock

tryLockの挙動とタイムアウトについて。

分かりやすい実例コードなので理解しやすいです。

ひしだま’s 技術メモページ:Javaマルチスレッド・排他処理

有名なTech系の検証・実装まとめサイト。

実行時間とかオーバーヘッドが気になる際に読むべき。

可視性についても、volatileの項にかなり詳しく載ってます。

IBM Developer:JDK 5.0における、より柔軟でスケーラブルなロック

2004年に書かれた、かなり古めのドキュメンテーション。

内容はとっつきづらいが、synchronizedとReentrantLockの使い分けについて書いてあります。

原子性と可視性について

個人ポストだけど分かりやすく、具体的な対応まで載ってます。

まとめ

synchronized修飾子では、順序性は保証されないが基本的な同期処理が実現できる。

ReentrantLockでは、順序性に加えて多機能だが実装が複雑化しやすい。

シーンに応じて使い分ける必要があるが、まずはsynchronized修飾子で要件十分かを確認する。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする