Java中的线程可以缓存最后一个字段吗?

Java中的线程可以缓存最后一个字段吗?,java,multithreading,Java,Multithreading,假设我声明了以下数组: private final int[] array = new int[10]; 现在,如果我启动10个线程,每个线程向自己的键写入一个值,线程1向数组[0]写入,线程2向数组[1]写入,等等,是否有任何线程尝试缓存数组,或者所有更改都将在主内存中进行?否,final对单个数组元素没有可见性语义,即使是一个volatile也只能保证参考突变本身而不是元素。为此,您需要同步或使用。否,final对于单个数组元素没有可见性语义,即使volatile也只能保证引用本身而不是元

假设我声明了以下数组:

private final int[] array = new int[10];

现在,如果我启动10个线程,每个线程向自己的键写入一个值,线程1向数组[0]写入,线程2向数组[1]写入,等等,是否有任何线程尝试缓存数组,或者所有更改都将在主内存中进行?

否,final对单个数组元素没有可见性语义,即使是一个volatile也只能保证参考突变本身而不是元素。为此,您需要同步或使用。否,final对于单个数组元素没有可见性语义,即使volatile也只能保证引用本身而不是元素上的变量。为此,您需要同步或使用更新 最后一个修饰符确保所有线程都可以看到该引用的初始化值。这一点很重要:final关键字仅指数组,而不指存储在该数组中的值

原始答案 我们不知道会发生什么。JVM可能缓存值,也可能不缓存值。我建议不要对此建立任何依赖关系

我想你需要一些能见度。通常,可以使用volatile关键字或同步强制可见性。在本例中,您有一个数组if整数

原子整数-s的数组 您可以将其替换为原子整数-s的数组:

但在这种情况下,您必须以不同的方式操作这些值:

array[0] = new AtomicInteger(0);
array[0].incrementAndGet();
并发列表 另一种方法是使用并发列表,如CopyOnWriteArrayList或使用Collections.synchronizedList包装方法:

// option 1
private final List<Integer> list = Collections.synchronizedList(new ArrayList<>());

// option 2
private final List<Integer> list = new CopyOnWriteArrayList<>();
并发映射 您还可以使用map,因此可以跳过有问题的初始化:

private final ConcurrentMap<Integer, Integer> map = new ConcurrentHashMap<>();
// ...
map.putIfAbsent(0, 0);
map.put(0, map.get(0) + 1);
使现代化 最后一个修饰符确保所有线程都可以看到该引用的初始化值。这一点很重要:final关键字仅指数组,而不指存储在该数组中的值

原始答案 我们不知道会发生什么。JVM可能缓存值,也可能不缓存值。我建议不要对此建立任何依赖关系

我想你需要一些能见度。通常,可以使用volatile关键字或同步强制可见性。在本例中,您有一个数组if整数

原子整数-s的数组 您可以将其替换为原子整数-s的数组:

但在这种情况下,您必须以不同的方式操作这些值:

array[0] = new AtomicInteger(0);
array[0].incrementAndGet();
并发列表 另一种方法是使用并发列表,如CopyOnWriteArrayList或使用Collections.synchronizedList包装方法:

// option 1
private final List<Integer> list = Collections.synchronizedList(new ArrayList<>());

// option 2
private final List<Integer> list = new CopyOnWriteArrayList<>();
并发映射 您还可以使用map,因此可以跳过有问题的初始化:

private final ConcurrentMap<Integer, Integer> map = new ConcurrentHashMap<>();
// ...
map.putIfAbsent(0, 0);
map.put(0, map.get(0) + 1);

线程可以缓存任何非易失性值

特别是,线程的代码可以内联到一个线程不会更改的布尔值的代码中。在重新编译之前,线程可能检测不到更改

在这种情况下,数组引用和数组中的值都将被缓存,但很可能会在一段时间后看到新值

使用final并没有真正的区别。一个原因是反射允许您更改最终字段

拥有这样的线程安全数组的一种更简洁的方法是使用Java5.0中添加的AtomicIntegerArray

final AtomicIntegerArray array = new AtomicIntegerArray(10);

public void increment(int n) {
    array.incrementAndGet(n);
}

线程可以缓存任何非易失性值

特别是,线程的代码可以内联到一个线程不会更改的布尔值的代码中。在重新编译之前,线程可能检测不到更改

在这种情况下,数组引用和数组中的值都将被缓存,但很可能会在一段时间后看到新值

使用final并没有真正的区别。一个原因是反射允许您更改最终字段

拥有这样的线程安全数组的一种更简洁的方法是使用Java5.0中添加的AtomicIntegerArray

final AtomicIntegerArray array = new AtomicIntegerArray(10);

public void increment(int n) {
    array.incrementAndGet(n);
}

这个问题是正确的,但可能问错了。问题是:你想做什么?是否要从另一个线程(如线程1)读取数组[0],该数组可能是由线程0编写的?在Java中,考虑主内存和CPU缓存没有什么意义,因为在Java运行时级别的规范中,它们并不总是这样存在。您要考虑的是在关系之前发生的情况,这大致保证了线程可以读取写操作,以及何时可以读取。这是指定的,也是您可以依赖的。如果您没有将某些机制放在适当的位置,线程就不能保证看到其他线程所做的更改。如果每个线程只是从自己的插槽中写入和读取,那么它们将始终看到自己的最新值。在我前面的评论中,缓存数组字段是一回事,缓存该字段的十个独立整数中的每一个又是另一回事,这也是重新构建问题框架以确保我们可以帮助您获得有效答案的原因。@matt但如果每个线程都被限制在其自己的数组索引内,那么shar就没有意义了
不管怎么说,都要删除数组。每个线程都应该使用一个字段或局部变量。这个问题是有效的,但可能问错了。问题是:你想做什么?是否要从另一个线程(如线程1)读取数组[0],该数组可能是由线程0编写的?在Java中,考虑主内存和CPU缓存没有什么意义,因为在Java运行时级别的规范中,它们并不总是这样存在。您要考虑的是在关系之前发生的情况,这大致保证了线程可以读取写操作,以及何时可以读取。这是指定的,也是您可以依赖的。如果您没有将某些机制放在适当的位置,线程就不能保证看到其他线程所做的更改。如果每个线程只是从自己的插槽中写入和读取,那么它们将始终看到自己的最新值。在我前面的评论中,缓存数组字段是一回事,缓存该字段的十个独立整数中的每一个又是另一回事,这也是重新设置问题框架以确保我们可以帮助您提供有效答案的原因。@matt但是如果每个线程都限于自己的数组索引,那么共享数组就没有意义了。每个线程都应该使用一个字段或局部变量。AtomicIntegerArray似乎是一个更自然的选择。谢谢,Peter!我甚至都不知道。我将浏览java.util.concurrent.atomic now.Re…数组,而不是…数组中存储的值。大多数有经验的程序员会立即理解您在那里说的话,但我认为,对于前来寻求帮助的新手来说,措辞不够明确。他们不完全理解数组变量不是数组。他们不知道数组的值只是一个指针,告诉JVM在堆的其他地方找到数组。无论如何,如果OP提问,我会很高兴地添加更多的例子。原子积分阵列似乎是一个更自然的选择。谢谢,彼得!我甚至都不知道。我将浏览java.util.concurrent.atomic now.Re…数组,而不是…数组中存储的值。大多数有经验的程序员会立即理解您在那里说的话,但我认为,对于前来寻求帮助的新手来说,措辞不够明确。他们不完全理解数组变量不是数组。他们不知道数组的值只是一个指针,告诉JVM在堆的其他地方找到数组。无论如何,如果OP提问,我会很高兴地添加更多的例子。