Сейчас я изучаю java.util.concurrent
. Я пытаюсь понять CopyOnWriteArrayList
.Использование CopyOnWriteArrayList в Java
Как я понял, этот класс выглядит как ArrayList
, но потокобезопасным. Этот класс очень полезен, если у вас много чтения и меньше написания.
Вот мой пример. Как я могу использовать его (только для цели обучения)?
Могу ли я использовать его таким образом?
package Concurrency;
import java.util.concurrent.*;
class Entry {
private static int count;
private final int index = count++;
public String toString() {
return String.format(
"index:%-3d thread:%-3d",
index,
Thread.currentThread().getId());
}
}
class Reader implements Runnable {
private CopyOnWriteArrayList<Entry> list;
Reader(CopyOnWriteArrayList<Entry> list) { this.list = list; }
public void run() {
try {
while(true) {
if(!list.isEmpty())
System.out.println("-out " + list.remove(0));
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
return;
}
}
}
class Writer implements Runnable {
private CopyOnWriteArrayList<Entry> list;
Writer(CopyOnWriteArrayList<Entry> list) { this.list = list; }
public void run() {
try {
while(true) {
Entry tmp = new Entry();
System.out.println("+in " + tmp);
list.add(tmp);
TimeUnit.MILLISECONDS.sleep(10);
}
} catch (InterruptedException e) {
return;
}
}
}
public class FourtyOne {
static final int nThreads = 7;
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Entry> list = new CopyOnWriteArrayList<>();
ExecutorService exec = Executors.newFixedThreadPool(nThreads);
exec.submit(new Writer(list));
for(int i = 0; i < nThreads; i++)
exec.submit(new Reader(list));
TimeUnit.SECONDS.sleep(1);
exec.shutdownNow();
}
}
Много чтения и почти не написано. На самом деле все просто. Всякий раз, когда ваш код вызывает метод, который будет изменять список, этот метод создает копию массива, он изменяет его копию, а затем он заменяет новую копию для старого с помощью одной атомной операции. Если какой-либо другой поток пытается получить доступ к списку в одно и то же время, другой поток будет либо обращаться к старой версии, либо к новой версии, но гарантированно никогда не получит доступ к версии, которая находится в некотором недействительном промежуточном состоянии. –
Вы используете ExecutorService (т. Е. Пул потоков) для выполнения задач, которые никогда не заканчиваются ('while (true) ...'). Не делай этого. Я не говорю, что это не сработает, но это не те пулы потоков. Я бы использовал 'new Thread (...)' или 'threadFactory.newThread (...)', чтобы создать поток, который никогда не заканчивается. –
Ваш потребитель (читатель) слепо полагает, что если 'list.isEmpty()' возвращает true, тогда в списке будет что-то в нем. Это наивное предположение. Это будет верно, когда есть только один потребитель, но во многих системах существует более одного потребителя. Вы должны привыкнуть писать код потребителя, который не будет генерировать исключение NullPointerException, если событие, которое его разбудило, уже обслуживается каким-то другим потребительским потоком. –