【问题标题】:Java PriorityQueue: Is it better to poll() and then add() or peek() and then remove()Java PriorityQueue:poll() 然后 add() 还是 peek() 然后 remove() 更好
【发布时间】:2015-10-02 09:35:53
【问题描述】:

我刚刚完成了我的联网作业队列,我有一个关于 Java 中 PriortiyQueue 性能的小问题。

获取此代码:

 private void performJob() {
    lock.lock();
    try {
        NetworkJob job = actions.poll();
        if (job.perform()) {
            return;
        }
        actions.add(job); //Job was a failure, add it back to the queue
    } finally {
        lock.unlock();
    }
}

在作业失败的情况下,作业仍然需要在队列中。所以,我的问题是:poll() 然后add()peek() 然后remove() 更好吗

我个人倾向于下面的代码,但考虑到工作不应该真的失败(在大多数情况下,假设它是通过)是不是更好poll()

 private void performJob() {
    lock.lock();
    try {
        NetworkJob job = actions.peek();
        if (!job.perform()) {
            return;
        }
        actions.remove(); //Job was a success,  we can remove it from the queue.
    } finally {
        lock.unlock();
    }
}

完全吹毛求疵,由于队列很少使用的性质,可能不值得担心,但我很感兴趣,我想看看你的推理。

完整代码:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;

import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public final class NetworkQueue implements Runnable {

    private final Context context;
    private final AtomicBoolean running = new AtomicBoolean(true);
    private final PriorityQueue<NetworkJob> actions = new PriorityQueue<>(5, new NetworkJobComparator());
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition jobReady = lock.newCondition();
    private final Condition networkUp = lock.newCondition();

    private ConnectionType connection = ConnectionType.NONE;

    public NetworkQueue(Context context) {
        this.context = context;
        context.registerReceiver(new NetworkListener(),
                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    @Override
    public void run() {
        try {
            while (running.get()) {
                waitJobAvailable();
                waitNetworkUp();
                performJob();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void setNetwork(ConnectionType net) {
        lock.lock();
        try {
            connection = net;
            if (connection != ConnectionType.NONE) {
                networkUp.signal();
            }
        } finally {
            lock.unlock();
        }
    }

    private void waitNetworkUp() throws InterruptedException {
        lock.lock();
        try {
            while (connection != ConnectionType.NONE) {
                networkUp.await();
            }
        } finally {
            lock.unlock();
        }
    }


    private void waitJobAvailable() throws InterruptedException {
        lock.lock();
        try {
            while (actions.isEmpty()) {
                jobReady.await();
            }
        } finally {
            lock.unlock();
        }
    }

    private void performJob() {
        lock.lock();
        try {
            NetworkJob job = actions.peek();
            if (!job.perform()) {
                return;
            }
            actions.remove();
        } finally {
            lock.unlock();
        }
    }

    public boolean addJob(NetworkJob job) {
        lock.lock();
        try {
            if (this.actions.contains(job)) {
                return false;
            }
            this.actions.add(job);
            this.jobReady.signal();
            return true;
        } finally {
            lock.unlock();
        }
    }

    public void end() {
        this.running.set(false);
    }

    private class NetworkListener extends BroadcastReceiver {

        ConnectivityManager conn = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);

        @Override
        public void onReceive(Context context, Intent intent) {
            NetworkInfo networkInfo = conn.getActiveNetworkInfo();
            if (networkInfo == null) {
                setNetwork(ConnectionType.NONE);
                return;
            }
            if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                setNetwork(ConnectionType.WIFI);
                return;
            }
            setNetwork(ConnectionType.ANY);
        }
    }

}

【问题讨论】:

  • 就性能而言,这可能(甚至可能主要)取决于您正在使用的队列的确切实现。除此之外,您应该注意 sn-ps 具有不同的语义,除非它们仅用于单线程。
  • @Marco13 我稍微扩展了 sn-p 以显示整个队列是多线程的,但代码块被锁定。语义不同是什么意思?
  • 如果 sn-ps 未被锁定(如在编辑版本中),那么同时执行 sn-ps 的两个线程将要么 poll 不同 元素(在第一个版本),或peek same 元素(在第二个版本中)。现在很清楚了,但仍然:这是一个 阻塞 队列吗?特别是PriorityBlockingQueue?
  • @Marco13 抱歉。不,这是手动锁定的 PriorityQueue。为了清楚起见,我添加了代码,我不使用 PriorityBlockingQueue 的原因是除了队列不为空之外,还有一个条件需要在添加元素后发出信号。
  • remove 实际上是 poll 带有额外的空检查。 poll/add 肯定比 peek 慢。你的代码做了不同的事情:在第一个 sn-p 你remove/add,第二个你只是删除。

标签: java performance collections queue


【解决方案1】:

在 OpenJDK 和 OracleJDK 中基于堆的标准 PriorityQueue 实现中,peek() 调用非常快:

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

那是因为堆根总是最小的元素。相比之下,删除和添加操作可能会非常昂贵,因为它们可能需要重组堆。因此peek/remove 解决方案可能会更快。

在我的库中,我有一个算法可以从未排序的输入中选择 n 最少的元素。我使用PriorityQueue 实现了它,它最多保留n 迄今为止发现的最少元素。第一个实现就像add/poll。当我 updated 使用 peek 时,性能得到了显着提升(在某些测试中高达 10 倍)。

【讨论】:

  • 这就是我要说的!太棒了,谢谢塔吉尔。
猜你喜欢
  • 2019-02-26
  • 1970-01-01
  • 1970-01-01
  • 2014-05-28
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多