Java 中的原始数据类型是否支持多线程安全?

40

在Java中,原始数据类型如intshort是否是线程安全的?我已经执行了以下代码,有时无法看到期望的结果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的值。

这里找到类似的示例。


9
不,它们不是线程安全的。如果您仅计划进行读取,可以考虑将成员变量设为“volatile”。如果您计划从不同线程读取和写入它,应将其设置为“synchronized”。 - Guillaume Polet
4个回答

68

以下三种情况是它们不安全的原因:

  • longdouble 甚至不能保证原子性更新(你可能会看到来自另一个线程的写操作一半)
  • 内存模型不能保证您在另一个线程中看到最新的更新,除非使用某种额外的内存屏障
  • 增加变量本身就不是原子性的

使用AtomicInteger等类进行线程安全操作。


10

1
关键点在于,如果原始类型被定义为对象实例的一部分,那么该原始类型将位于堆上,而不是线程安全的。 - petertc
3
奇怪,Oracle 文档实际上表示对于 int 类型,原始类型访问是原子性的:https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html - Yamcha
1
@Parvin,除了long和double之外,原始类型保证是原子的。如果您正在读取或写入该值,则对于该操作,它是线程安全的。 - Pacerier
链接无效 - “嗯...无法访问此页面 | ERR_CONNECTION_TIMED_OUT”。 - Pang

4
我建议在java.util.concurrent.atomic中使用类,它们专门设计用于线程安全。在某些情况下,JVM可以利用硬件功能进行优化。

0
  1. 在多线程环境中读取/写入值时,程序应该具有适当的同步或锁定以防止数据竞争。这与要访问的数据类型无关。在理想的世界中,我们应该不共享或仅共享不可变对象,这始终是线程安全的。

  2. 理论上,根据https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7,长/双精度甚至不能保证是原子性的。然而,在实现中往往是原子性的,在我的环境(64位ubuntu 18.04,Intel 64位CPU,Oracle JDK 8)中,以下代码在有或没有volatile关键字的情况下都不会输出任何内容,因此在这种情况下它是原子性的,我猜测适用于所有Intel / AMD 64 CPU。 我们也可以对双精度执行相同的操作,尽管构造具有特定属性的双精度值进行检查有点棘手。

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);
    }
}

欢迎终于在stackoverflow上发布问题。请详细说明您帖子中所述的 It:该问题的代码仅显示对 inT 的并发写入,而该变量既不是 double 也不是 long - greybeard
LongThreadSafe.value是由多个线程写入和读取的值。已经用~~~将代码括起来,谢谢。 - Jacky_Cai
你的帖子开头的 It 指的是什么还不清楚:问题中唯一明确的问题是关于 types 的,如果要引用这些类型,应该使用 They - greybeard
你是对的,我更新了答案以涵盖线程安全和类型。 - Jacky_Cai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接