可迭代的Java列表Roundrobin并发问题

时间:2018-11-28 03:00:48

标签: java concurrency

在很多情况下,我需要设置一个List,然后以循环方式遍历它们。我对多线程技术还比较陌生,所以我正在学习。

今天我开始遇到一些并发问题。

Exception in thread "LogThing: 25" Exception in thread "LogThing: 27" Exception in thread "LogThing: 21" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.google.common.collect.Iterators$2.next(Iterators.java:418)
    at com.example.my.RoundRobinIterable.getNext(RoundRobinIterable.java:22)
    at com.example.my.EpsSendingStrategy.run(EpsSendingStrategy.java:22)
    at java.lang.Thread.run(Thread.java:745)

现在,RoundRobbinIterable非常简单。

import java.util.Iterator;
import java.util.List;

import com.google.common.collect.Iterables;

public class RoundRobinIterable<T> {
  private final Iterator<T> elements;

  public RoundRobinIterable(final List<T> elements) {
    this.elements = Iterables.cycle(elements).iterator();
  }

  public synchronized T getNext() {
    return this.elements.next();;
  }

}

现在EpsSendingStrategy.java具有一个static RoundRobinIterable<PrintWriter> writerIterable,并且您可以从stacktrace中看到它也是一个线程。创建RoundRobinIterable时,我将一些值传递给Collections.synchronizedList(new ArrayList<>())。第22行恰好是writer = writerIterable.getNext();

所以getNext()导致java.util.ConcurrentModificationException异常

编辑1:

public class EpsSendingStrategy extends SendingStrategy {

  public EpsSendingStrategy(LogDosSettings settings) {
    super(settings);
    logger = LogManager.getLogger(EpsSendingStrategy.class);
  }

  @Override
  public void run() {
    createConnections();
    startSending.set(System.nanoTime());
    PrintWriter writer;

    while (sendMessages) {
      logger.debug("about to write message");
      writer = writerIterable.getNext();
      writer.println(settings.getMessage());
      writer.flush();

      lastSent.set(startSending.get() + logItoration.get() * 1000000000L / settings.getEps());
      while (System.nanoTime() < lastSent.get());

      logItoration.incrementAndGet();
    }
  }

}

编辑2:我也尝试过在https://stackoverflow.com/a/4493759/2599884上使用相同的同步,但这也很失败。还有我不了解的更深层次的东西。

2 个答案:

答案 0 :(得分:1)

Collections.synchronizedList()的javadoc中所述:-

Iterator i = list.iterator(); // Must be in synchronized block

但是您要在这一行中将同步列表传递给番石榴:-

this.elements = Iterables.cycle(elements).iterator();

其中I think在调用.iterator()时不使用同步。另外,它可以在不受您控制的时间调用它(例如您对其进行修改时)。

尽管此答案可能会为您指明正确的方向,但值得等待某个人提供更明确的答案(我从未使用过番石榴)。

答案 1 :(得分:1)

抛出

ConcurrentModificationException 是因为您在某些线程将元素添加到列表中时在列表上进行了迭代。

您正在使用 Collections.synchronizedList ,其中添加和删除方法通过列表锁进行同步。要禁止其他人在使用迭代器迭代列表时向列表添加内容,您必须像这样锁定该列表。

synchronized(list){
Iterator i = list.iterator(); 
      while (i.hasNext()){
        //do something
  }
}

您没有执行此操作,您正在同步 getNext 方法,但没有使用列表锁,并且仅在调用 next()方法时才进行同步。足够。

 public synchronized T getNext() {
    return this.elements.next();;
  }

如果要使用模式,则必须在RoundRobinIterable中创建列表的副本

import java.util.Iterator;
import java.util.List;

import com.google.common.collect.Iterables;

public class RoundRobinIterable<T> {
  private final Iterator<T> elements;

  public RoundRobinIterable(final List<T> elements) {
    List<T> copyOfElements = new ArrayList<>(elements);
    this.elements = Iterables.cycle(copyOfElements).iterator();
  }

  public synchronized T getNext() {
    return this.elements.next();;
  }
}

现在,在迭代列表时,没有人可以将元素添加到复制的列表中,但是创建RoundRobinIterable之后,您将不会获得添加到原始列表中的元素。如果要获取稍后添加的元素,那么最好使用除List之外的其他方法。例如 ConcurrentLinkedQueue。