Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/370.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 是否有必要使基元实例变量可变?_Java_Multithreading_Volatile - Fatal编程技术网

Java 是否有必要使基元实例变量可变?

Java 是否有必要使基元实例变量可变?,java,multithreading,volatile,Java,Multithreading,Volatile,为了试验多线程概念,我正在实现我自己的AtomicInteger版本,它使用悲观锁。它看起来像这样: public class ThreadSafeInt { public int i; // Should this be volatile? public ThreadSafeInt(int i) { this.i = i; } public synchronized int get() { return i; }

为了试验多线程概念,我正在实现我自己的AtomicInteger版本,它使用悲观锁。它看起来像这样:

public class ThreadSafeInt {
    public int i; // Should this be volatile?

    public ThreadSafeInt(int i) {
        this.i = i;
    }

    public synchronized int get() {
        return i;
    }

    public synchronized int getAndIncrement() {
        return this.i++;
    }
    // other synchronized methods for incrementAndGet(), etc...
}
我编写了一个测试,以ThreadSafeInt的一个实例为例,将它提供给数百个线程,并使每个线程调用getAndIncrement 100000次。我所看到的是,所有增量都正确发生,整数的值正好是
(线程数)*(每个线程的增量数)
,即使我没有在基本实例变量
I
上使用volatile。我预计,如果我没有将
I
设置为volatile,那么我会遇到很多可见性问题,例如,线程1将
I
从0增加到1,但是线程2仍然看到0的值,并且只将其增加到
1
,导致最终值小于正确值

我知道可见性问题是随机发生的,并且可能取决于我的环境的属性,因此即使存在固有的可见性问题,我的测试也可以正常工作。所以我倾向于认为volatile关键字仍然是必要的

但这是正确的吗?或者我的代码中是否有一些属性(可能只是一个基本变量,等等),我可以信任这些属性来避免使用volatile关键字

即使我没有在原语实例变量I上使用volatile。我希望如果我不让我变得易变,那么我会遇到很多可见性问题

通过使
getAndIncrement()
get()
方法
synchronized
,所有正在修改
i
的线程都将正确地锁定它以进行更新和检索值。
synchronized
块使得
i
不必是
volatile
,因为它们也确保了内存同步

也就是说,您应该使用一个
AtomicInteger
,它包装了一个
volatile int
字段
AtomicInteger
getAndIncrement()
方法更新值,而不必求助于
同步的
块,该块在保持线程安全的同时速度更快

public final AtomicInteger i = new AtomicInteger();
...
// no need for synchronized here
public int get() {
    return i.get();
}
// nor here
public int getAndIncrement() {
    return i.getAndIncrement();
}
我会遇到很多可见性问题,例如,线程1将I从0增加到1,但线程2仍然看到0的值,并且只将其增加到1,导致最终值小于正确值

如果您的
get()
方法未
synchronized
,那么您的增量可能会得到正确处理,但其他线程将无法正确发布
i
的值。但由于这两种方法都是同步的,这确保了读写时的内存同步
synchronized
还执行锁定,以便您可以执行
i++
。同样,
AtomicInteger
处理内存同步和增量竞争条件的效率更高


更具体地说,当进入
同步
块时,它会跨越一个读取存储器屏障,这与从
易失性
字段读取相同。当退出
synchronized
块时,它会跨越写入内存障碍,这与写入
volatile
字段相同。与
synchronized
块的不同之处在于,还有一个锁定,以确保一次只有一个人锁定特定对象。

您是说synchronized不仅保证代码路径一次只能由一个线程执行,但除此之外,它还保证在同步块内部接触的任何变量都被视为不稳定的?我认为,即使使用synchronize,两个线程也有可能观察到变量中的不同值-因此我认为您的意思是,情况并非如此。@russell这真正的意思是,
I++
通常不是原子操作,但由于
synchronized
而使其成为原子操作。“@russel当写入和读取方法都同步时,就像在代码中一样,普通字段变量被视为易失性变量。学习“Java内存模型”以开发有意义的测试程序。我在问题@russell的末尾添加了一些内容,以解释内存障碍以及
volatile
synchronized
之间的相似性(和差异)。