java原始的设计或意外原子?

Jus*_*eff 41 java multithreading

那个java原始整数(int)是原子的吗?两个共享一个int的线程的一些实验似乎表明它们,但当然没有证据表明它们不是并不暗示它们是.

具体来说,我跑的测试是这样的:

public class IntSafeChecker {
    static int thing;
    static boolean keepWatching = true;

    // Watcher just looks for monotonically increasing values   
    static class Watcher extends Thread {
        public void run() {
            boolean hasBefore = false;
            int thingBefore = 0;

            while( keepWatching ) {
                // observe the shared int
                int thingNow = thing;
                // fake the 1st value to keep test happy
                if( hasBefore == false ) {
                    thingBefore = thingNow;
                    hasBefore = true;
                }
                // check for decreases (due to partially written values)
                if( thingNow < thingBefore ) {
                    System.err.println("MAJOR TROUBLE!");
                }
                thingBefore = thingNow;
            }
        }
    }

    // Modifier just counts the shared int up to 1 billion
    static class Modifier extends Thread {
        public void run() {
            int what = 0;
            for(int i = 0; i < 1000000000; ++i) {
                what += 1;
                thing = what;
            }
            // kill the watcher when done
            keepWatching = false;
        }
    }

    public static void main(String[] args) {
        Modifier m = new Modifier();
        Watcher w = new Watcher();
        m.start();
        w.start();
    }
}
Run Code Online (Sandbox Code Playgroud)

(这只是在32位Windows PC上尝试使用java jre 1.6.0_07)

实质上,Modifier将计数序列写入共享整数,而Watcher检查观察值是否永远不会减少.上的机器,其中一个32位的值有四个独立的字节(或甚至两个16位字)将被访问,将有一个概率观察者会赶上在不一致的,半更新的状态的共享整数,并且检测值减少而不是增加.无论(假设的)数据字节是收集/写入LSB 1st还是MSB 1st,这都应该有效,但最多只能是概率.

考虑到今天的宽数据路径,即使java规范不需要它,32位值也可能是有效的原子,这似乎是非常可能的.实际上,使用32位数据总线,您可能需要更加努力地获得对字节的原子访问而不是32位整数.

谷歌搜索"java原始线程安全"会在线程安全的类和对象上显示大量内容,但是查找基元上的信息似乎是在大海捞针中寻找谚语.

Jon*_*eet 60

Java中的所有内存访问都是原子的,除了longdouble(可能是原子的,但不一定是).说实话并不是清楚,但我相信这就是含义.

从JLS 第17.4.3节开始:

在顺序一致的执行中,对于与程序的顺序一致的所有单个动作(例如读取和写入)存在总顺序,并且每个单独的动作是原子的并且对于每个线程立即可见.

然后在17.7:

一些实现可能发现将64位长或双值上的单个写操作划分为相邻32位值上的两个写操作是方便的.为了效率,这种行为是特定于实现的; Java虚拟机可以原子地或分两部分对long和double值执行写入.

请注意,原子性与波动性非常不同.

当一个线程将一个整数更新为5时,它保证另一个线程不会看到1或4或任何其他中间状态,但没有任何明显的波动或锁定,另一个线程可以永远看到0.

关于努力获得字节的原子访问,你是对的:VM可能必须努力......但它确实必须这样做.从规范的第17.6节:

某些处理器不提供写入单个字节的能力.通过简单地读取整个字,更新适当的字节,然后将整个字写回存储器,在这样的处理器上实现字节数组更新是违法的.这个问题有时被称为单词撕裂,并且在处理器上无法轻易地单独更新单个字节,将需要一些其他方法.

换句话说,由JVM决定是否正确.

  • @BradEllis:两件事:首先,成为原子并不意味着对值的更改立即可见,但是`AtomicInteger`提供了这一点。其次,“ AtomicInteger”提供了一些选项来“修改”原子值-而诸如“ x + = 2;”之类的则不是“原子操作”。如果x以0开头,则一个线程执行x + = 1;而另一个线程执行x + = 2 ;,则x的最终结果可能是1、2或3。 AtomicInteger`,等效操作将*总是*得到3。 (3认同)

Rob*_*anu 27

  • 没有多少测试可以证明线程安全 - 它只能反驳它;
  • 我发现在间接引用JLS 17.7该条规定

一些实现可能发现将64位长或双值上的单个写操作划分为相邻32位值上的两个写操作是方便的.

并进一步下来

出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位半写一次.

这似乎意味着对int的写入是原子的.

  • +1"没有多少测试可以证明线程安全 - 它只能反驳它;" (14认同)
  • @dfa - 这就是我所说的"没有证据证明他们不是并不暗示他们是" (6认同)