我对Java中的volatile
关键字有疑问(在阅读了几个问题之后,特别是this document)。
我有一系列线程从名为cardArray
的int数组中读取信息,如果数组中不存在条目,我想创建它。我使用的是一个同步方法,如:
public synchronized void growArray(int id) {
if(getIndex(id) == -1) {
cardArray = Arrays.copyOf(cardArray, cardArray.length + 1);
cardArray[cardArray.length - 1] = id;
}
}
getIndex(int id)是一个返回数组中唯一id的位置的方法,如果数组不存在则返回-1。 cardArray被宣布为:
protected volatile int[] cardArray = new int[10];
这背后的想法是两个线程可能想要使阵列同时增长。由于growArray()
方法是同步的,因此它们必须逐个进行。一旦第一个线程使数组增长,后续线程就不应该这样做,因为getIndex(id)应该表明id已经存在。然而,事实是数组被声明为volatile
而不是它的元素让我怀疑这是否是实际行为。
这会发生什么,或者访问growArray()
的以下线程是否仍然试图使阵列增长?如果是这样,除了原子阵列之外还有其他选择吗?我正在考虑(如果这不能按我的意愿工作)在写完元素后添加cardArray = cardArray
,但我不知道它是否会有所作为。
答案 0 :(得分:1)
根据您的要求,您可能希望使用ArrayList或HashSet或LinkedHashSet。如果您要用Java编写大量代码,那么熟悉Java集合库将会非常有用。
以下是一些可用选项的解释:
http://docs.oracle.com/javase/tutorial/collections/implementations/index.html
如果您决定使用ArrayList,您的实现将如下所示:
// This is how you might create your list.
private List<Integer> myList = new ArrayList<Integer>();
public synchronized void growArray(int id) {
if( ! cardList.contains(id) ){
cardList.add(id);
}
}
如果您使用HashSet(保证唯一性并自动增长),您可以这样做:
// This is how you might create your list.
private Set<Integer> myList = new HashSet<Integer>();
public synchronized void growArray(int id) {
cardList.add(id);
}
希望这有帮助!
答案 1 :(得分:0)
我没有看到任何问题。据我所知,cardArray
变量是易变的。您正在更改引用,因为它是volatile
,第二个线程应该看到新值并使用更新的数组。
问题我看到的是,如果两个线程同时尝试插入相同的 id
,那么如果重要的话,你最终会得到一个重复的值。因此,如果一个线程看到缺少值,它将开始创建一个新数组来插入它。但同时如果另一个数组正在检查相同的值,它将等待第一个线程完成其工作。然后插入值而不检查它是否已存在。你可以通过再次检查值来解决这个问题。
另一个问题是,如果您有并发线程读取数组,那么数组将会很短,但最后一个值将不是您所期望的。您可以尝试创建数组,更新最后一个值,最后更改引用它的变量。
正如评论所说,您最好使用其他数据结构。
答案 2 :(得分:0)
这是会发生什么,或者以下线程是否可以访问 growArray()仍然试图让数组增长?
如果一个线程存在getIndex(id) == -1
为false的方法,那么进入该方法的任何线程也会看到此更新。这是因为该方法是同步的。事实上,你可以完全删除volatile,它仍然有用。
但是,由于您正在更新volatile,因此任何非synchronized
访问都将受益于volatile写入,因此您可能希望将其声明为这样。