【问题标题】:Volatile access to array element对数组元素的可变访问
【发布时间】:2014-08-04 00:14:45
【问题描述】:

当涉及到数组时,我对 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,但我不知道它是否会有所作为。

【问题讨论】:

  • 为什么是数组?还有其他更适合调整大小的数据结构。另外,getIndex(int) 是做什么的?
  • 我不是 Java 数据结构方面的专家,我可以使用什么其他结构来有效调整大小? getIndex(int id) 搜索数组以查看 id 是否已作为条目包含(for 循环,数组未排序)。如果是,则返回其索引。如果不是,-1。
  • 我会使用HashSet 或其中一个队列,具体取决于要求。 getIndex() 同步了吗? Do you call it anywhere else? 是否有其他(非同步?)函数与数组交互?如果答案都是no,你不必担心(不妨让数组同步)。否则,您可能需要进行一些额外的检查。
  • @nosid 是的,读取方法(它们都没有写入)。

标签: java arrays multithreading volatile


【解决方案1】:

我认为这没有任何问题。据我了解,cardArray 变量是易变的。您正在更改引用,因为它是 volatile,所以第二个线程应该会看到新值并使用更新后的数组。

我看到的问题是,如果您的两个线程都尝试同时插入相同的id,那么如果这很重要,您最终会得到一个重复的值。因此,如果一个线程看到缺少值,它将开始创建一个新数组来插入它。但同时,如果另一个数组正在检查相同的值,它将等待第一个线程完成它的工作。然后插入值而不检查它是否已经存在。您可以通过再次检查该值来解决此问题。

另一个问题是,如果您有并发线程读取数组,则数组会在短时间内变长,但最后一个值不会是您所期望的。您可以尝试创建数组,更新最后一个值并最后更改引用它的变量。

正如 cmets 所说,使用其他数据结构会更好。

【讨论】:

  • 这就是该方法被同步的原因:一次只有一个线程可以使用它,所以无论哪个第二个线程都会进入该方法但不应该做任何事情。另外,您是否阅读了我在问题中链接的文档?
  • @user2891462,你是对的。应该没问题。我没有看到你的getIndex(id) 方法调用。
  • 对不同的数据结构有什么建议吗?
  • @user2891462,不确定您在做什么,但请参阅 stackoverflow.com/questions/6720396/…stackoverflow.com/questions/6992608/…
【解决方案2】:

这是会发生什么还是可能以下线程访问 growArray() 还在尝试让数组增长吗?

如果线程存在 getIndex(id) == -1 为 false 的方法,则随后进入该方法的任何线程也将看到此更新。这是因为方法是同步的。事实上,你可以完全删除 volatile,它仍然可以工作。

但是,由于您正在更新 volatile,因此任何非synchronized 访问都将受益于 volatile 写入,因此您可能希望以这种方式声明它。

【讨论】:

    【解决方案3】:

    根据您的要求,您可能希望使用 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);
    }
    

    希望这会有所帮助!

    【讨论】:

    • 谢谢!我想我将使用 ConcurrentHashMap(因为我使用该数组作为另一个关联对象数组的索引)。 ConcurrentHashMap 包括使用putIfAbsent 对表进行原子添加,显然在我的情况下消除了外部同步的需要。
    猜你喜欢
    • 2014-06-10
    • 1970-01-01
    • 2012-11-26
    • 1970-01-01
    • 2020-11-19
    • 2019-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多