有一个简单的任务:许多线程调用MyClass.add()
函数,并且一个线程试图为它们提供服务
我的问题:哪种解决方案更好或更有效?
第一种方法:使用CopyOnWriteArrayList
@Singleton
public class myClass {
List<myType> list = new CopyOnWriteArrayList<myType>();
boolean isRunning = false;
//this is called from many threads
public void add(myType x){
list.add(x);
}
//this is called from 1 thread
public void start(){
if (isRunning) return;
isRunning = true;
while (!list.isEmpty()) {
myType curr = list.remove(0);
//do something with curr...
}
isRunning = false;
}
}
采用简单锁定的第二种方法:
@Singleton
public class myClass {
List<myType> list = new ArrayList<myType>();
boolean isRunning = false;
private final Lock _mutex = new ReentrantLock(true);
//this is called from many threads
public void add(myType x){
_mutex.lock();
list.add(x);
_mutex.unlock();
}
//this is called from 1 thread
public void start(){
if (isRunning) return;
isRunning = true;
while (!list.isEmpty()) {
_mutex.lock();
myType curr = list.remove(0);
_mutex.unlock();
//do something with curr...
}
isRunning = false;
}
}
第3种方法:使用ConcurrentLinkedQueue
@Singleton
public class myClass {
ConcurrentLinkedQueue<myType> list = new ConcurrentLinkedQueue<myType>();
boolean isRunning = false;
//this is called from many threads
public void add(myType x){
list.add(x);
}
//this is called from 1 thread
public void start(){
if (isRunning) return;
isRunning = true;
while (!list.isEmpty()) {
//list cannot be empty at this point: other threads can't remove any items
myType curr = list.poll();
//do something with curr...
}
isRunning = false;
}
}
这是最初的错误解决方案。我不知道它为什么有时给出(&gt; 100个线程)ConcurrentModificationException
(尽管有迭代器和“同步”):
@Singleton
public class myClass {
List<myType> list = Collections.synchronizedList(new ArrayList<myType>());
boolean isRunning = false;
//this is called from many threads
public void add(myType x){
synchronized(list) {
list.add(x);
}
}
//this is called from 1 thread
public void start(){
if (isRunning) return;
isRunning = true;
for (ListIterator<myType> iter = list.listIterator(); iter.hasNext();){
myType curr = iter.next();
//do something with curr...
synchronized(list) {
iter.remove(); //sometime it gives ConcurrentModificationException!
}
}
isRunning = false;
}
}
答案 0 :(得分:1)
一般规则是:最适合您问题的规则。
锁变量正在减慢一切,因为所有线程如果进入锁定部分就会被置于保持状态,即使不需要它(如果有5个元素,5个线程可以同时轮询它们,仅第六个必须等待)。但是,如果您拥有永远无法共享的单一资源(例如网络连接或文件),则此解决方案很好。
CopyOnWriteArrayList是最好的解决方案,如果你的线程很少写,但经常读。与写入相关的成本要高得多,可以通过更快的读取来补偿(与ConcurrentLinkedQueue相比)。但是你的代码主要是写的,所以这对你来说不是一个好的解决方案。
如果读取和写入的数量大致相等,则ConcurrentLinkedQueue是最佳解决方案,因此名称为Queue。所以它应该适合你的情况。
此外,您的代码中存在严重错误:
while (!list.isEmpty()) {
myType curr = list.poll();
该列表只保证每个调用都是以原子方式完成的,但是您的代码并不是因为您使用它而自动进行线程安全的。在此示例中,列表可能已在isEmpty()
和poll()
之间进行了修改,因此它可能在isEmpty()调用中有1个元素,但在轮询后则没有。这是由ConcurrentLinkedQueue优雅地处理,返回null,但不是由您的代码。所以正确的形式是:
myType curr;
while ((curr = list.poll()) != null) {
由于poll是一个原子 - 因此是线程安全的 - 调用,它将返回一个元素或不返回。之前发生的事情以及之后发生的事情由于线程而未定义,但您可以确定这个单一调用(在后台执行更多功能)将始终完美运行。
对于remove(0)
调用也是如此,如果最后一个元素已被另一个线程删除,它可以抛出IndexOutOfBoundsException
。