哪个是java中更高效的线程安全队列

时间:2013-11-27 15:53:49

标签: java multithreading arraylist queue

有一个简单的任务:许多线程调用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;
    }
}

1 个答案:

答案 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