在同步数组列表上调用 toArray() 线程安全操作?

问题描述 投票:0回答:2

我正在创建和使用同步数组列表,该列表正在被多个线程访问/修改。在某个操作中我想删除这个同步列表的所有元素并处理它们。

List<String> myList = Collections.synchronizedList(new ArrayList<>());

// adding to array
public void addElements(String s) {
   myList.add(s)
}

public void removeAllAndProcess() {
  Arrays.stream(myList.toArray()).forEach(s -> {
            myList.remove(s);
            process(s.toString());
        });
}

// Some processing
public void process(String s) {
  // if processing unsuccessful then again add to list : myList.add(s)
}

我想知道

toArray()
是否需要在同步块内?在调用
toArray
时并发添加到列表中是否会影响返回的数组?

java multithreading synchronized
2个回答
1
投票

Collections.synchronizedList
的 Javadoc 说:

用户在通过 Iterator、Spliterator 或 Stream 遍历返回列表时必须手动同步它

我不知道我们是否应该考虑那个处方适用于

List#toArray
。我会这么认为,但显然不是。

我去 OpenJDK 看源代码

ArrayList
here。显然
ArrayList#toArray
实现是:

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

Arrays.copyOf
的源代码最终调用
System.arrayCopy
.

所以我们有一堆代码。而且它们都没有明确承诺线程安全。因此,虽然我无法直接回答您的问题,但我可以提供一个简单的替代解决方案。

AtomicReference

我会建议这个简单的解决方案作为替代方案:与其耗尽元素列表,不如替换整个列表,同时使用

AtomicReference
提供线程安全。

但是,我们需要使用这种方法在

addElement
方法上进行同步。一个线程可能会获得对列表的引用,第二个线程可能会用新列表替换列表并开始对旧列表进行迭代,而第一个线程会修改它仍然引用的旧列表。

虽然我们必须向

addElement
方法添加同步,但我们可以从列表本身中删除同步。在这种方法下,我们不再从列表中删除。我们对列表所做的唯一修改是通过现在同步的
addElement
方法添加。所以不需要打电话
Collections.synchronizedList
.

对了,我把复数

addElements
改成了单数
addElement

AtomicReference < List < String > > myListRef = new AtomicReference( new ArrayList<>() ) ;

// adding to array
public synchronized void addElement( String s ) {
    myListRef.get().add( s ) ;
}

public void removeAllAndProcess() {
    List < String > newList = new ArrayList<>() ;
    List < String > oldList = myListRef.getAndSet( newList ) ;
    oldList.forEach( ( String s ) -> {
            myList.remove( s ) ;
            process( s );
        }
    );
}

// Some processing
public void process(String s) {
  boolean processedSuccessfully = … ;
  if( ! processedSuccessfully ) { 
    myListRef.get().add( s ) ;
  }
}

注意:我在这里的假设是您的

process
方法是only曾经从您的
removeAllAndProcess
方法中调用过。


我对你的代码安排不满意。但是我忽略了这些问题,而是按照您的要求专注于您的问题。

我怀疑另一种数据结构,例如

Queue
会更适合您的目的。但是,我又一次忽略了这一点,而是按照您的要求专注于您的问题。


0
投票

不需要额外的同步,因为

Collections.synchronizedList(xyz)
安全地包装了对底层
xyz
的所有访问。

您可以通过在一个好的 IDE 中跟踪源代码来仔细检查您的 JDK 实现。在 OpenJDK20 中,您会看到

Collections.synchronizedList
返回一个
SynchronizedList

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

SynchronizedList
扩展了
SynchronizedCollection
实现了
toArray()
与对象同步以确保您有线程安全的操作:

public Object[] toArray() {
    synchronized (mutex) {return c.toArray();}
}
© www.soinside.com 2019 - 2024. All rights reserved.