「こんにちは、アミーゴ! 私たちは万能薬、つまりあらゆる病気を治す薬を持っています。すでに見たように、制御されていないスレッドの切り替えは問題です。」

「なぜスレッド自体が、いつ次のスレッドに切り替えるかを決定できないのでしょうか? やるべきことをすべて実行してから、「完了しました!」という合図を送ります。」

「スレッド自体にスイッチングの制御を許可すると、さらに大きな問題になります。コードの書き方が悪く、スレッドが決して CPU を放棄しないとします。昔は、これが仕組みでした。そして、それは非常に悪夢でした。」

「わかりました。それで、解決策は何ですか?」

他のスレッドをブロックする。 これが仕組みです。」

スレッドが共有オブジェクトやリソースを使用しようとすると、互いに干渉することが明らかになりました。コンソール出力の例で見たように、1 つのコンソールとそこにすべてのスレッドが出力されます。めちゃくちゃです。

そこで、特別なオブジェクトであるmutexが発明されました。それは、バスルームのドアに「空室あり/占有中」と書かれた標識のようなものです。これには 2 つの状態があります:オブジェクトは利用可能または占有されています。これらの状態は、「ロック」および「ロック解除」とも呼ばれます。

スレッドが他のスレッドと共有するオブジェクトを必要とする場合、そのオブジェクトに関連付けられたミューテックスをチェックします。ミューテックスがロック解除されている場合、スレッドはそれをロックし («占有» としてマークし)、共有リソースの使用を開始します。スレッドがその処理を完了すると、ミューテックスのロックが解除されます («利用可能» としてマークされます)。

スレッドがオブジェクトの使用を希望しており、ミューテックスがロックされている場合、スレッドは待機中にスリープします。占有しているスレッドによってミューテックスが最終的にロック解除されると、スレッドはすぐにロックを解除して実行を開始します。バスルームのドアのサインとの類似性は完璧です。

「それでは、ミューテックスをどのように操作すればよいのでしょうか? 特別なオブジェクトを作成する必要があるのでしょうか?」

「それよりもはるかに単純です。Javaの作成者は、このミューテックスを Object クラスに組み込みました。したがって、作成する必要さえありません。これはすべてのオブジェクトの一部です。すべてがどのように機能するかは次のとおりです。」

コード 説明
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap メソッドは、name1 変数と name2 変数の値を交換します。

2 つのスレッドから同時に呼び出された場合はどうなるでしょうか?

実際のコードの実行 最初のスレッドのコード 2番目のスレッドのコード
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//other thread is running
name2 = s1;
//the thread waits until the mutex is unlocked

String s2 = name1;
name1 = name2;
//other thread is running
//other thread is running
name2 = s2;
結論
変数の値は 2 回交換され、元の場所に戻りました。

キーワード synchronizedに注意してください。

「はい、どういう意味ですか?」

「同期済みとしてマークされたコードのブロックにスレッドが入ると、Java マシンは synchronized という単語の後に括弧内に示されたオブジェクトのミューテックスを直ちにロックします。スレッドがそのブロックを離れるまで、他のスレッドはこのブロックに入ることができません。スレッドが離れるとすぐに、ブロックが同期済みとしてマークされると、ミューテックスは直ちに自動的にロックが解除され、別のスレッドで取得できるようになります。」

ミューテックスが占有されている場合、スレッドは停止して解放されるのを待ちます。

「とてもシンプルでとてもエレガント。素晴らしいソリューションですね。」

「はい。でも、この場合はどうなると思いますか?」

コード 説明
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
swap メソッドと swap2 メソッドは、同じミューテックス( thisオブジェクト) を共有します。

あるスレッドが swap メソッドを呼び出し、別のスレッドが swap2 メソッドを呼び出した場合はどうなりますか?

「ミューテックスが同じであるため、2 番目のスレッドは、最初のスレッドが同期ブロックを離れるまで待機する必要があります。そのため、同時アクセスには問題はありません。」

「やったね、アミーゴ!正解だよ!」

ここで、 synchronized はコードのブロックだけでなくメソッドをマークするためにも使用できることを指摘したいと思います。それが意味するのは次のとおりです。

コード 本当に何が起こるのか
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}