Javaにおけるロック可能オブジェクトパターン:堅牢な同期メカニズムの実装
別名
- リソースロック
- 相互排除オブジェクト
ロック可能オブジェクトデザインパターンの目的
Javaにおけるロック可能オブジェクトパターンは、マルチスレッド環境で共有リソースへのアクセスを制御することを目的としています。リソースのロックメカニズムを提供することによりスレッドセーフ性を確保し、一度に1つのスレッドのみがリソースにアクセスできるようにします。
現実世界の例を用いたロック可能オブジェクトパターンの詳細な説明
現実世界の例
Javaにおけるロック可能オブジェクトデザインパターンの現実世界の例として、混雑したオフィスにある共有プリンターを想像してみてください。このパターンは、一度に1つのスレッドのみがリソースにアクセスできるようにすることで、並行性制御と同期を維持します。複数の従業員が一日を通してドキュメントを印刷する必要がありますが、プリンターは一度に1つの印刷ジョブしか処理できません。これを管理するために、プログラミングにおけるロック可能オブジェクトと同様に、ロックシステムが導入されています。これは、ある人が印刷しているときに、他の人は順番を待つ必要があることを保証します。これにより、印刷ジョブが重複したり、互いに干渉したりするのを防ぎ、各ドキュメントが正しく、送信された順序で印刷されるようにします。これは、ソフトウェア開発におけるスレッド同期とリソースロックの概念を反映しています。
簡単な言葉で
ロック可能オブジェクトデザインパターンは、ロックメカニズムを通じて一度に1つのスレッドのみがリソースにアクセスできるようにすることで、マルチスレッド環境で共有リソースへの安全なアクセスを保証します。
Wikipediaによると
コンピュータサイエンスでは、ロックまたはミューテックス(相互排除から)は、複数のスレッドが同時に状態を変更またはアクセスするのを防ぐ同期プリミティブです。ロックは相互排除並行性制御ポリシーを強制し、さまざまな可能な方法により、さまざまなアプリケーションに複数の独自の実装が存在します。
Javaにおけるロック可能オブジェクトパターンのプログラム例
ロック可能オブジェクトパターンは、Javaにおける並行性制御デザインパターンであり、一度に1つのスレッドのみが共有リソースにアクセスできるようにすることで、相互排除を保証し、データの破損を防ぎます。同期するメソッドに`synchronized`キーワードを使用する代わりに、Lockableインターフェースを実装するオブジェクトがリクエストを処理します。
この例では、`Lockable`インターフェースを実装する`SwordOfAragorn`オブジェクトがあります。`Elf`、`Orc`、`Human`クラスで表される複数の`Creature`オブジェクトが、剣を取得しようとしています。各`Creature`は、`Runnable`を実装する`Feind`オブジェクトにラップされており、各クリーチャーが別々のスレッドで剣を取得しようとすることができます。
`Lockable`インターフェースは次のとおりです
public interface Lockable {
boolean isLocked();
Creature getLocker();
boolean acquire(Creature creature);
void release(Creature creature);
}
`SwordOfAragorn`クラスはこのインターフェースを実装します
public class SwordOfAragorn implements Lockable {
// Implementation details...
}
`Creature`クラスとそのサブクラス(`Elf`、`Orc`、`Human`)は、剣を取得しようとすることができる異なるクリーチャーを表します
public abstract class Creature {
// Implementation details...
}
public class Elf extends Creature {
// Implementation details...
}
public class Orc extends Creature {
// Implementation details...
}
public class Human extends Creature {
// Implementation details...
}
`Feind`クラスは、`Creature`と`Lockable`オブジェクトをラップし、`Runnable`を実装します
public class Feind implements Runnable {
private final Creature creature;
private final Lockable target;
public Feind(@NonNull Creature feind, @NonNull Lockable target) {
this.creature = feind;
this.target = target;
}
@Override
public void run() {
if (!creature.acquire(target)) {
fightForTheSword(creature, target.getLocker(), target);
} else {
LOGGER.info("{} has acquired the sword!", target.getLocker().getName());
}
}
// Additional methods...
}
`App`クラスでは、複数の`Feind`オブジェクトが作成され、それぞれが別々のスレッドで`ExecutorService`に送信されます
public class App implements Runnable {
@Override
public void run() {
var sword = new SwordOfAragorn();
List<Creature> creatures = new ArrayList<>();
// Creation of creatures...
ExecutorService service = Executors.newFixedThreadPool(totalFiends);
for (var i = 0; i < totalFiends; i = i + MULTIPLICATION_FACTOR) {
service.submit(new Feind(creatures.get(i), sword));
service.submit(new Feind(creatures.get(i + 1), sword));
service.submit(new Feind(creatures.get(i + 2), sword));
}
// Additional code...
}
}
この例は、複数のスレッドが共有リソースのロックを取得しようとすることができ、一度に1つのスレッドのみがロックを取得できることを示すことにより、ロック可能オブジェクトパターンを示しています。
現実世界の例を用いたロック可能オブジェクトパターンの詳細な説明

Javaでロック可能オブジェクトパターンを使用する場合
- 複数のスレッドが共有リソースに同時にアクセスすることによるデータの破損を防ぎ、スレッドセーフ性と堅牢な共有リソース管理を確保する必要がある場合は、Javaでロック可能オブジェクトパターンを使用します。
- スレッドセーフ性が重要であり、さまざまな操作全体でデータの整合性を維持する必要があるシステムに適しています。
Javaにおけるロック可能オブジェクトパターンの実際のアプリケーション
- Javaのsynchronizedキーワードとjava.util.concurrent.locksパッケージのLockインターフェースは、同期を管理するためにロック可能オブジェクトを実装します。
ロック可能オブジェクトパターンの利点とトレードオフ
利点
- データの整合性を確保し、競合状態を防ぎます。
- 共有リソースへのアクセスを管理するための明確な構造を提供します。
トレードオフ
- ロックの取得と解放のオーバーヘッドにより、パフォーマンスが低下する可能性があります。
- 注意深く実装および管理しないと、デッドロックが発生する可能性があります。
関連するJavaデザインパターン
- モニターオブジェクト:どちらのパターンも共有リソースへのアクセスを管理します。モニターオブジェクトは、条件変数の同期とカプセル化を組み合わせます。
- リーダー/ライターロック:読み取り操作が書き込み操作よりも多いシナリオ向けのロック可能オブジェクトの特殊化。