Java - 同期
こんにちは、未来のJavaの魔法使いたち!今日は、Javaプログラミングの中で最も重要な概念の一つ、同期について深く掘り下げます。プログラミングに新手であるのであれば心配しないでください。私はこの旅をステップバイステップにガイドしてくれます。これまでの教えの中で無数の学生たちにもそうしてきたように。だから、お気に入りの飲み物を持って、快適な場所に座って、一緒にこの素晴らしい冒険に出発しましょう!
同期とは何で、なぜそれが必要でしょうか?
忙しいキッチンで複数のシェフが複雑な料理を作る際、彼らが全員協調のなく材料を取ろうとすると、どうなるでしょうか?カオスが起こるのです!これは、Javaプログラムで複数のスレッドが同時に共有リソースにアクセスしようとするときに起こることと同じです。そんなときに同期が登場します!
Javaの同期は、その忙しいキッチンにおける交通警察のような存在で、一度に一つのシェフ(スレッド)だけが特定の材料(リソース)を使用できるようにします。これにより、秩序を維持し、競合を防ぎ、プログラムが予期せぬ驚きなしにスムーズに動作するようにします。
スレッド同期の必要性
実際の例を見て、同期がなぜ重要であるかを理解しましょう:
public class BankAccount {
private int balance = 1000;
public void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " は引き出そうとしています...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " は " + amount + " を引き出しました");
} else {
System.out.println(Thread.currentThread().getName() + " には残高が不足しています");
}
}
public int getBalance() {
return balance;
}
}
この例では、シンプルな BankAccount
クラスと withdraw
メソッドをお見せしています。簡単に見えますよね?しかし、二人が同時にお金を引き出そうとするとどうなるでしょう?見てみましょう!
public class UnsynchronizedBankDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount();
Thread john = new Thread(() -> {
account.withdraw(800);
}, "John");
Thread jane = new Thread(() -> {
account.withdraw(800);
}, "Jane");
john.start();
jane.start();
try {
john.join();
jane.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最終残高: " + account.getBalance());
}
}
このプログラムを実行すると、以下のような結果が表示されることがあります:
John は引き出そうとしています...
Jane は引き出そうとしています...
John は 800 を引き出しました
Jane は 800 を引き出しました
最終残高: -600
待て、どうして負の残高になっていますか?これ、私たちの友達が呼ぶレースコンディションです。ジョンとジェーンはどちらも残高を確認し、十分なお金があると判断して引き出しました。これが、なぜ私たちが同期を必要とするのかの良い例です!
Javaでの同期の実装
問題を見つけたので、Javaがそれをどのように解決してくれるのかを見ていきましょう。Javaは複数の方法で同期を実装する手助けをしていますが、最も一般的な二つに焦点を当てます:
- 同期メソッド
- 同期ブロック
同期メソッド
最も簡単に同期を追加する方法は、メソッド宣言に synchronized
キーワードを使用することです。 BankAccount
クラスを修正してみましょう:
public class BankAccount {
private int balance = 1000;
public synchronized void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " は引き出そうとしています...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " は " + amount + " を引き出しました");
} else {
System.out.println(Thread.currentThread().getName() + " には残高が不足しています");
}
}
public int getBalance() {
return balance;
}
}
この同期メソッドを使用してプログラムを実行すると、ずっとより理にかなった出力が得られます:
John は引き出そうとしています...
John は 800 を引き出しました
Jane には残高が不足しています
最終残高: 200
もっと良いですね!一度に一つのスレッドだけが withdraw
メソッドを実行できるようになり、先ほどの問題が防ぎられます。
同期ブロック
時々、全てのメソッドを同期したくない場合もあります。その場合には、同期ブロックを使用できます。以下にその方法を示します:
public class BankAccount {
private int balance = 1000;
private Object lock = new Object(); // これが私たちのロックオブジェクトです
public void withdraw(int amount) {
synchronized(lock) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " は引き出そうとしています...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " は " + amount + " を引き出しました");
} else {
System.out.println(Thread.currentThread().getName() + " には残高が不足しています");
}
}
}
public int getBalance() {
return balance;
}
}
これでも先ほどの同期メソッドと同じ結果を得ることができ、コードの中で同期させる部分をより細かく制御することができます。
正しい同期の重要性
今、あなたは「素晴らしい!私は全部同期させよう!」と考えているかもしれませんが、ちょっと待ってください!過剰な同期はパフォーマンスの問題を引き起こす可能性があります。それは、小さな町のすべての交差点に交通信号機を設置するようなもので、安全かもしれませんが、全てが遅くなってしまいます。
鍵は、必要な部分だけを同期することです。私たちの銀行口座の例では、残高を確認し更新する部分だけを同期する必要がありました。
一般的な同期メソッド
Javaは同期に役立つメソッドをいくつか提供しています。以下にいくつかの一般的なものを表にします:
メソッド | 説明 |
---|---|
wait() |
現在のスレッドを他のスレッドが notify() または notifyAll() を呼び出すまで待機させます |
notify() |
このオブジェクトのモニターを待っている一つのスレッドを起床させます |
notifyAll() |
このオブジェクトのモニターを待っているすべてのスレッドを起床させます |
join() |
スレッドが終了するのを待機します |
これらのメソッドは、より複雑な同期シナリオに役立ちます。
結論
それでは、皆さん!私たちはJavaの同期の旅を終えました。なぜそれが必要であるのかから、コードに実装するまでを探求しました。同期は、調味料を料理に加えるようなもので、プログラムを強化するのに十分なだけを使うことができますが、すべてを圧倒するのではありません。
あなたのJavaの冒険が続く中で、より複雑な同期シナリオに遭遇することがありますが、心配しないでください!この基盤を持っていれば、あなたはどんなマルチスレッディングの挑戦にも立ち向かえます。
コーディングを続け、学び続け、そして最も重要なのは、楽しんでください!毕竟、プログラミングは技術とも艺术でもあります。次回まで、あなたのスレッドが同期され、Javaが強くなることを祈っています!
Credits: Image by storyset