Каждый раз, когда вызывается remove
, создается новая копия внутреннего массива, поддерживающего CopyOnWriteArrayList
.Доступ к этому списку в будущем с помощью методов доступа и мутатора будет виден при обновлении.
Однако , метод метода CopyOnWriteArrayList#foreach
выполняет итерацию в массиве, который был доступен во время его вызова,Это означает, что все выполнения метода flush
, который вошел в foreach
до , любое обновление в списке будет повторяться в устаревшей версии массива.
Таким образом, во время параллельноговыполнения метода flush
, одни и те же элементы Audit
будут сохраняться более одного раза и до d
раз, если d
- это число максимальных одновременных выполнений метода flush
.
Другая проблема с использованием CopyOnWriteArrayList
в этом случае заключается в том, что для каждого вызова remove
создается новая копия, сложность вашего кода составляет d.n^2
, где n
- длина списка, а d
определено выше.
CopyOnWriteArrayList
не является правильной реализацией для использования здесь.Есть несколько возможных подходящих конструкций.Одним из них является использование LinkedBlockingQueue
следующим образом [*]:
public void flush(Audit... audits) {
Collection<Audit> auditCollection = Arrays.asList(audits);
this.queue.addAll(auditCollection);
Collection<Audit> poissonedAudits = new ArrayList<Audit>();
Audit audit = null;
while ((audit = this.queue.poll()) != null) {
try {
// save audit object on database
queue.remove(audit);
} catch (DataAccessException e) {
// log it
poissonedAudits.add(audit);
}
}
this.queue.addAll(poissonedAudits);
}
Вызов LinkedBlockingQueue#poll()
является потокобезопасным и атомарным.Один и тот же элемент никогда не может опрашиваться несколько раз (если он не добавлен в очередь более одного раза, cf [*]).Сложность линейна в n
.
Следует учитывать два момента:
- Ваша коллекция аудита не должна содержать
null
элементов, поскольку это запрещено для LinkedBlockingQueue
,потому что null
используется для указания того, что список пуст. - Вы должны позаботиться о том, чтобы не использовать методы блокировки, такие как
take
из poll(timeout, unit)
для опроса очереди.
[*] Произошло изменение в методе flush
, который new не выполняет копирование элементов Audit
.Я не уверен, есть ли гарантия, что эти элементы различны, если метод flush
вызывается параллельно.Если массив Audit
одинаков для всех вызовов flush
, очередь должна заполняться только один раз, перед любым вызовом flush
.