1

我有一个项目列表,当我检查一个项目时,它位于列表的末尾。我希望应用程序是线程安全的。

关于列表“项目”,“checkItem”线程的代码是否安全?

  public class ItemListImpl implements ItemList {

        List<Item> items = Collections.synchronizedList(new ArrayList<Item>());

        /**
         * Checks an item and brings it at the end of the list
         */
        public void checkItem(int index) {
            Item item = items.get(index);
            item.check();

            synchronized (items) {
                items.remove(item);
                items.add(items.size(), item);
            }

        }
    }

(忘记我使用项目索引的事实。我将使用对象或 id)

4

2 回答 2

2

我们需要意识到这items是一个同步列表,并且一个同步列表使用它自己作为它的原始互斥体。这意味着所有单独的列表操作以及synchronized块正在同步items

因此,事情可能以非同步方式发生的唯一点是get调用完成和synchronized块开始之间的间隙。那么这是一个问题吗?

那么让我们考虑一下可能发生的事情:

  • 如果其他线程移动了 的位置Item,那没关系。无论如何,我们会发现只是将它移到最后。

  • 如果其他线程删除了Item,那没关系。我们的调用remove将 return false,但我们仍将添加 Item 回来。

仅当给定可能Item出现在列表中的多个位置时才会出现问题items。如果发生这种情况,那么代码可能会将位于错误位置的 items 元素移动到列表的末尾;例如

  1. 线程 A 调用 get(42) 来获取项目 I。
  2. 线程 B 在位置 1 插入项目 I。
  3. 线程 A 完成了检查,并删除并重新添加了第 I 项……但由于我现在也出现在位置 1,它实际上删除并添加了该副本,而不是位置 42 的副本。

简而言之,如果我们可以假设给定的 Item 对象在列表中最多出现一次,那么代码就是线程安全的。如果我们不能假设,那么就有可能将对象移动到错误的位置。

还应该注意的是,在给定的 Item 可以出现多次的情况下,代码将无法正常工作。问题是remove(item)删除了item对它找到的实例的第一个引用,不一定是在 position 的那个index。有两种方法来看待这个问题。如果此行为是无意的,则它是一个错误。如果它是有意的,那么线程安全问题可能不是问题。


无论哪种方式,代码对于内存模型都是安全的,假设该check方法是线程安全的。

于 2012-08-17T02:34:43.213 回答
0

进行以下更改以使其成为线程安全的。

 public void checkItem(int index) {
            synchronized (items){    
                Item item = items.get(index);
                item.check();          
                items.remove(item);
                items.add(items.size(), item);
            }

    }
于 2012-08-17T02:19:01.827 回答