当前位置: 首页 > 面试题库 >

使用foreach遍历ArrayList时的线程安全

壤驷宏才
2023-03-14
问题内容

我有一个ArrayList正在实例化并填充在后台线程上的代码(我用它来存储Cursor数据)。同时,可以在主线程上对其进行访问,并通过使用foreach对其进行迭代。因此,这显然可能导致引发异常。

我的问题是使此类类字段成为线程安全的而不每次都复制它或不使用标志的最佳实践是什么?

class SomeClass {

    private final Context mContext;
    private List<String> mList = null;

    SomeClass(Context context) {
        mContext = context;
    }

    public void populateList() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                mList = new ArrayList<>();

                Cursor cursor = mContext.getContentResolver().query(
                        DataProvider.CONTENT_URI, null, null, null, null);
                try {
                    while (cursor.moveToNext()) {
                        mList.add(cursor.getString(cursor.getColumnIndex(DataProvider.NAME)));
                    }
                } catch (Exception e) {
                    Log.e("Error", e.getMessage(), e);
                } finally {
                    if (cursor != null) {
                        cursor.close();
                    }
                }
            }
        }).start();
    }

    public boolean searchList(String query) { // Invoked on the main thread
        if (mList != null) {
            for (String name : mList) {
                if (name.equals(query) {
                    return true;
                }
            }
        }

        return false;
    }
}

问题答案:

通常,对不是线程安全的数据结构进行并发操作是一个非常糟糕的主意。您无法保证将来的实现不会改变,这可能会严重影响应用程序的运行时行为,即java.util.HashMap被并发修改时会导致无限循环。

为了同时访问List,Java提供了java.util.concurrent.CopyOnWriteArrayList。使用此实现将以多种方式解决您的问题:

  • 它是线程安全的,允许并发修改
  • 遍历列表快照不受并发添加操作的影响,从而允许并发添加和迭代
  • 比同步快

或者,如果 使用 内部 数组的副本是 严格的
要求(我无法想象您的情况,则该数组相当小,因为它仅包含对象引用,可以非常有效地将其复制到内存中),您可以同步地图上的访问。但这将需要正确初始化Map,否则您的代码可能会抛出a,NullPointerException因为不能保证线程执行的顺序(您假设populateList()之前已开始执行,因此列表已初始化。使用同步块时,请选择保护的明智地进行阻止。run()
在同步块中使用方法,读取器线程必须等待,直到处理完游标的结果为止(这可能需要一段时间),因此您实际上失去了所有并发性。

如果您决定使用同步块,则将进行以下更改(并且我不主张它们是完全正确的):

初始化列表字段,以便我们可以同步对其的访问:

private List<String> mList = new ArrayList<>(); //initialize the field

同步修改操作(添加)。不要从同步块内部的游标中读取数据,因为如果它是低延迟操作,则在该操作期间无法读取mList,这会阻塞所有其他线程一段时间。

//mList = new ArrayList<>(); remove that line in your code
String data = cursor.getString(cursor.getColumnIndex(DataProvider.NAME)); //do this before synchronized block!
synchronized(mList){
  mList.add(data);
}

读取迭代必须在同步块内部,因此在迭代时不会添加任何元素

synchronized(mList){ 
  for (String name : mList) {
    if (name.equals(query) {
      return true;
    }
  }
}

因此,当两个线程在列表上运行时,一个线程可以添加单个元素,也可以一次遍历整个列表。您没有在代码的这些部分上并行执行。

关于名单(即同步的版本VectorCollections.synchronizedList())。那些性能可能较差,因为在同步时您实际上失去了简单执行,因为一次只能有一个线程运行受保护的块。此外,它们可能仍然容易出现ConcurrentModificationException,甚至可能在单个线程中发生。如果在迭代器创建之间修改了数据结构,则应该抛出该异常。因此,这些数据结构不会解决您的问题。

我也不建议手动同步,因为简单地做错了的风险就太大了(在错误或不同的监视上进行同步,太大的同步块,…)

TL; DR

用一个
java.util.concurrent.CopyOnWriteArrayList



 类似资料:
  • 问题内容: 我可以在varArgs上使用还是在varArgs上使用? 问题答案: 变量参数的行为与数组相似,因此您可以使用来获取变量变量中的一个: 您可以使用以下方法遍历它们:

  • 问题内容: 有没有办法循环遍历一个?Xcode说 通用结构’ForEach’要求’[String:Int]’符合’RandomAccessCollection’ 所以有没有办法使Swift字典符合,或者因为字典是无序的,那是不可能的吗? 我尝试过的一件事是迭代字典的键: 但不是s 的数组,而是类型(不确定何时更改)。我知道我可以编写一个辅助函数,该函数接受字典并返回键的数组,然后可以迭代该数组,但

  • 需要帮助通过简单的HTML DOM解析器遍历DOM树。如果有人能花点时间来看看它是如何工作的,那么如果我能理解的话,知识渊博的PHP程序员应该能很快理解它。我陷入困境的地方是从我朋友的俄罗斯篮球网站上的一个部门那里得到文章。例如,以下是博客div的外观: 这就是我如何拉出单个“a”链接并显示它们的方法,但我想做的是获取所有链接的div标题,基本上对它们进行分类,而不是将所有内容都排成一行。我不知道

  • 《 Java Iterator遍历Collection集合元素》一节中主要讲解如何使用 Iterator 接口迭代访问 Collection 集合里的元素,除了这个方法之外,我们还可以使用 Java 5 提供的 foreach 循环迭代访问集合元素,而且更加便捷。如下程序示范了使用 foreach 循环来迭代访问集合元素。 输出结果为: 小牛知识库C++教程 小牛知识库C语言教程 小牛知识库Jav

  • 问题内容: 我有两个数组列表,声明为: 这两个字段都完全包含“值的相同”,实际上在自然界中是对应的。 我知道我可以遍历这样的循环之一: 但是,我想同时遍历两个列表。我知道它们的尺寸完全相同。我怎么做? 问题答案: 您可以使用: