int
&short
のようなプリミティブデータ型はJavaでスレッドセーフですか?次のコードを実行したところ、予想される結果500が表示されないことがありました。
public class SampleThree extends Thread
{
static long wakeUpTime = System.currentTimeMillis() + (1000*20);
static int inT;
public static void main(String args[])
{
System.out.println("initial:" + inT);
for(int i=0; i<500; i++)
new SampleThree().start();
try {
Thread.sleep(wakeUpTime - System.currentTimeMillis() + (1000*30));
System.out.println("o/p:" + inT);
}
catch(Exception e){
e.printStackTrace();
}
}
public void run()
{
try {
long s = wakeUpTime - System.currentTimeMillis();
System.out.println("will sleep ms: " + s);
Thread.sleep(s);
inT++; // System.out.println(inT);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
ここで同時に500スレッドがint変数inT
を更新します。同時更新が完了するのを待った後のメインスレッドは、inT
値を出力します。
同様の例を見つける here
安全ではない方法が3つあります。
long
とdouble
は、アトミックに更新されることさえ保証されていません(別のスレッドからの書き込みの半分を見ることができます)スレッドセーフな操作には AtomicInteger
などを使用します。
プリミティブ型はスレッドセーフではありません。 this チュートリアルを確認してください。
Java.util.concurrent.atomicのクラスを使用することをお勧めします。これらはスレッドセーフのために設計されており、場合によっては、JVMはハードウェア機能を利用して最適化できます。
マルチスレッド環境で値の読み取り/書き込みを行うには、プログラムが適切に同期またはロックして、データの競合を防ぐ必要があります。アクセスするデータ型とは関係ありません。理想的な世界では、何も共有しないか、常にスレッドセーフである不変オブジェクトのみを共有する必要があります。
理論的には、 https://docs.Oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7によれば、long/doubleに対してアトミックであることが保証されていません。 ただし、実装はアトミックになりがちです。次のコードは、私の環境(64ビットubuntu 18.04、Intel 64ビットCPU、Oracle JDK 8)で揮発性キーワードの有無にかかわらず何も出力しないため、この状況ではアトミックです。すべてのIntel/AMD 64 CPUに適用されます。同じことをdoubleでも行うことができますが、特定のプロパティをチェックしてdouble値を作成するのは少し難しいです。
public class LongThreadSafe {
// multiple threads read and write this value.
// according to the Java spec, only volatile long is guaranteed to be atomic
private static long value = 0;
private static final int max = (1 << 30) - 1;
private static final int threadCount = 4;
static ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
static CyclicBarrier barrier = new CyclicBarrier(threadCount);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
// all threads start to work at the same time
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
for (int j = 1; j < max; j++) {
// read value into v2
long v2 = value;
// check v2
int low = (int) v2;
int high = (int) (v2 >> 32);
if ((high << 1) != low) {
System.out.println("invalid number found high=" + high + ", low=" + low);
}
// write LongThreadSafe.value again
LongThreadSafe.value = ((long) j << 32) | (long) (j << 1);
}
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.MINUTES);
}
}