gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-02 09:06:21 25 4
gpt4 key购买 nike

我有一个 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 在并发修改时会导致无限循环。

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

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

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

如果您决定使用同步块(synchronized block),我会进行以下更改(我不声称它们是完全正确的):

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

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

同步修改操作(添加)。不要从同步块(synchronized block)内的游标读取数据,因为如果它是低延迟操作,则在该操作期间无法读取 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 block)内,因此在迭代时不会添加任何元素:

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

因此当两个线程对列表进行操作时,一个线程可以一次添加单个元素或遍历整个列表。您无法并行执行代码的这些部分。

关于列表的同步版本(即 VectorCollections.synchronizedList())。这些可能性能较低,因为通过同步,您实际上失去了并行执行,因为一次只有一个线程可以运行 protected block 。此外,它们可能仍然容易发生 ConcurrentModificationException,这甚至可能发生在单个线程中。如果数据结构在迭代器创建和迭代器应该继续进行之间被修改,则抛出。所以这些数据结构无法解决您的问题。

我也不推荐手动同步,因为简单地做错的风险太高(在错误或不同的监控上同步,同步块(synchronized block)太大,......)

长话短说

使用 java.util.concurrent.CopyOnWriteArrayList

关于java - 使用 foreach 遍历 ArrayList 时的线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38845778/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com