私は意図的にスレッドを可視性の問題を作成しようとしていたと私は予想外の結果を得ました:
public class DownloadStatus {
private int totalBytes;
private boolean isDone;
public void increment() {
totalBytes++;
}
public int getTotalBytes() {
return totalBytes;
}
public boolean isDone() {
return isDone;
}
public void done() {
isDone = true;
}
}
public class DownloadFileTask implements Runnable {
DownloadStatus status;
public DownloadFileTask(DownloadStatus status) {
this.status = status;
}
@Override
public void run() {
System.out.println("start download");
for (int i = 0; i < 10_000; i++) { //"download" a 10,000 bytes file each time you run
status.increment(); //each byte downloaded - update the status
}
System.out.println("download ended with: " + status.getTotalBytes()); //**NOTE THIS LINE**
status.done();
}
}
//creating threads, one to download, another to wait for the download to be done.
public static void main(String[] args) {
DownloadStatus status = new DownloadStatus();
Thread t1 = new Thread(new DownloadFileTask(status));
Thread t2 = new Thread(() -> {
while (!status.isDone()) {}
System.out.println("DONE!!");
});
t1.start();
t2.start();
}
だから、これは可視性の問題を作成し実行している - それは最初のスレッドでバックを書かれてしまった前に、それをキャッシュしていたので、第二のスレッドは、更新された値は表示されません - これは無限ループ(ながら)、第二のスレッドが常にチェックしている原因キャッシュされたisDone()。(少なくとも、それは私はそれがうまくいくと思う方法です)。
私は呼び出している2番目のコードブロックから行をコメントアウトすると、この可視性の問題が起こって停止した理由を私は取得しない事がありますstatus.getTotalBytes()
。私が理解から、両方のスレッドは、あるように、第2のスレッドが常に(最初のスレッドによって更新され、新しい値が表示されない本質的と)彼のキャッシュされた値をチェックする必要がありますので、ステータスオブジェクトをキャッシュすることから始めます。
なぜ、この行は、この可視性の問題を引き起こしてステータスオブジェクトのメソッドを呼び出していますか?(そしてもっと興味深いこと - なぜ呼び出していない、それはそれを修正します?)
あなたが「可視性の問題」と呼ぶことは、実際にデータ競合です。
単一のスレッドは、彼らが書かれているために、その事業の効果を見ています。それはあなたが変数を更新し、それを読めば、あなたは常にそのスレッド内で更新された値が表示されますです。
別のスレッドから見たときに、スレッドの実行の効果が異なる場合があります。これは主に言語と基盤となるハードウェアアーキテクチャに関連しています。レジスタの値を維持しながら、コンパイラは、命令、遅延メモリへの書き込みを並べ替えたり、メインメモリに書き込まれる前に、値がキャッシュに保持することができます。明示的なメモリバリアがなければ、メインメモリの値が更新されることはありません。それはあなたが、「可視性の問題」と呼んでいるものです。
これは、メモリバリアがあると思われますSystem.println
。あなたがその行を実行するときに、その時点までのすべての更新プログラムは、メインメモリにコミットされ、他のスレッドがそれを見ることができます。明示的な同期せずに、これらのスレッドは、彼らが前にその変数のために得た値を再使用することができますので、他のスレッドが、それを見るという保証がまだ存在しないことに注意してください。値が他のスレッドによって変更することができることをコンパイラ/ランタイムを伝えるプログラムでは何もありません。