在 Java 中使用 CopyOnWriteArrayList
Using CopyOnWriteArrayList in Java
我正在学习java.util.concurrent
。我试图理解 CopyOnWriteArrayList
。
据我了解,此 class 看起来像 ArrayList
,但线程安全。这个class如果你是多读少写的,非常有用。
这是我的例子。我该如何使用它(仅用于学习目的)?
我可以这样使用吗?
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();
}
}
请注意,在您的示例中,您的一位作者的写作速度是给定 reader 的 10 倍,导致制作了大量副本。另请注意,您的 reader(s) 也在列表上执行写入操作 (remove()
)。
在这种情况下,您正在以惊人的高速率写入列表,导致严重的性能问题,因为每次更新此列表时都会使用大量内存。
CopyOnWriteArrayList 仅在同步开销成为问题并且读取与结构修改的比率很高时使用。一个或多个 reader 尝试同时访问列表时所看到的性能提升会分摊整个数组副本的成本。这与传统的同步列表形成鲜明对比,在传统同步列表中,每次访问(读取或写入)都在某个互斥锁下进行控制,因此只有一个线程可以同时对列表执行某些操作。
如果需要简单的线程安全列表,请考虑 Collections.synchronizedList()
提供的同步列表。
另请注意:
if(!list.isEmpty()){
System.out.println("-out " + list.remove(0));
}
不是有效的编程,因为无法保证在 if 语句计算后列表 不会 为空。为了保证一致的效果,您需要直接检查 list.remove()
的 return 值或将整个段包装在 synchronized
块中(破坏使用线程安全结构的目的).
remove()
调用是一个结构修改调用,也应替换为类似 get()
的方法,以确保在读取数据时不进行结构修改。
总而言之,我认为 CopyOnWriteArrayList 只需要以非常特定的方式使用,并且只有在传统同步变得慢得无法接受时才需要使用。虽然您的示例在您自己的计算机上可能运行良好,但将访问量级缩放得更大,您将导致 gc 做太多工作来维护堆 space.
我正在学习java.util.concurrent
。我试图理解 CopyOnWriteArrayList
。
据我了解,此 class 看起来像 ArrayList
,但线程安全。这个class如果你是多读少写的,非常有用。
这是我的例子。我该如何使用它(仅用于学习目的)?
我可以这样使用吗?
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();
}
}
请注意,在您的示例中,您的一位作者的写作速度是给定 reader 的 10 倍,导致制作了大量副本。另请注意,您的 reader(s) 也在列表上执行写入操作 (remove()
)。
在这种情况下,您正在以惊人的高速率写入列表,导致严重的性能问题,因为每次更新此列表时都会使用大量内存。
CopyOnWriteArrayList 仅在同步开销成为问题并且读取与结构修改的比率很高时使用。一个或多个 reader 尝试同时访问列表时所看到的性能提升会分摊整个数组副本的成本。这与传统的同步列表形成鲜明对比,在传统同步列表中,每次访问(读取或写入)都在某个互斥锁下进行控制,因此只有一个线程可以同时对列表执行某些操作。
如果需要简单的线程安全列表,请考虑 Collections.synchronizedList()
提供的同步列表。
另请注意:
if(!list.isEmpty()){
System.out.println("-out " + list.remove(0));
}
不是有效的编程,因为无法保证在 if 语句计算后列表 不会 为空。为了保证一致的效果,您需要直接检查 list.remove()
的 return 值或将整个段包装在 synchronized
块中(破坏使用线程安全结构的目的).
remove()
调用是一个结构修改调用,也应替换为类似 get()
的方法,以确保在读取数据时不进行结构修改。
总而言之,我认为 CopyOnWriteArrayList 只需要以非常特定的方式使用,并且只有在传统同步变得慢得无法接受时才需要使用。虽然您的示例在您自己的计算机上可能运行良好,但将访问量级缩放得更大,您将导致 gc 做太多工作来维护堆 space.