5

我正在尝试在 Java ME 中实现一个简单的阻塞队列。在 JavaME API 中,Java SE 的并发实用程序不可用,所以我不得不像过去一样使用等待通知。

这是我的临时实现。我使用notify而不是notifyAll因为在我的项目中有多个生产者但只有一个消费者。我故意使用一个对象来等待通知以提高可读性,尽管它浪费了一个参考:

    import java.util.Vector;

    public class BlockingQueue {    
        private Vector queue = new Vector();
        private Object queueLock = new Object();    

        public void put(Object o){
            synchronized(queueLock){
                queue.addElement(o);
                queueLock.notify();
            }       
        }

        public Object take(){
            Object ret = null;
            synchronized (queueLock) {
                while (queue.isEmpty()){
                    try {
                        queueLock.wait();
                    } catch (InterruptedException e) {}
                }

                ret = queue.elementAt(0);
                queue.removeElementAt(0);
            }
            return ret;
        }
    }

我的主要问题是关于put方法。我可以把这queue.addElement条线放在外面synchronized吗?如果是这样,性能会提高吗?

此外,这同样适用于take:我可以将这两个操作queue排除在外synchronized block吗?

还有其他可能的优化吗?

编辑:
正如@Raam 正确指出的那样,消费者线程在wait. 那么有什么替代方法可以防止这种情况发生呢?(注意:在 JavaME 中,我没有来自 Java SE 的所有这些好类。把它想象成旧的 Java v1.2)

4

4 回答 4

1

Vector 类不保证线程安全,您应该像您所做的那样同步对它的访问。除非您有证据表明您当前的解决方案存在性能问题,否则我不会担心。

另一方面,我认为使用notifyAll而不是notify支持多个消费者没有什么害处。

于 2012-04-26T08:54:13.987 回答
1

synchronized用于保护对共享状态的访问并确保原子性。

请注意,方法Vector已经同步,因此Vector保护它自己的共享状态本身。因此,您的同步块只需要确保您的操作的原子性。

您当然不能queue从方法中的同步块继续操作take(),因为原子性对于该方法的正确性至关重要。但是,据我了解,您可以从put()方法中的同步块中移动队列操作(我无法想象会出错的情况)。

然而,上面的推理纯粹是理论上的,因为在所有情况下你都有双重同步:你的 synchronize onqueueLockVector隐式 synchronize on的方法queue因此提出的优化没有意义,它的正确性取决于双重同步的存在。

为避免双重同步,您还需要同步queue

synchronized (queue) { ... }

另一种选择是使用非同步集合(例如ArrayList)而不是Vector,但 JavaME 不支持它。在这种情况下,您也将无法使用建议的优化,因为同步块还保护非同步集合的共享状态。

于 2012-04-26T09:09:00.153 回答
0

除非您特别由于垃圾收集而出现性能问题,否则我宁愿使用链表而不是 Vector 来实现队列(先进先出)。

当您的项目(或另一个)获得多个消费者时,我还将编写可以重用的代码。尽管在这种情况下,您需要注意 Java 语言规范并没有强加实现监视器的方法。实际上,这意味着您无法控制通知哪个消费者线程(现有的 Java 虚拟机中有一半使用 FIFO 模型实现监视器,而另一半使用 LIFO 模型实现监视器)

我还认为使用阻塞类的人也应该处理 InterruptedException。毕竟,否则客户端代码将不得不处理 null Object 返回。

所以,像这样:


/*package*/ class LinkedObject {

    private Object iCurrentObject = null;
    private LinkedObject iNextLinkedObject = null;

    LinkedObject(Object aNewObject, LinkedObject aNextLinkedObject) {
        iCurrentObject = aNewObject;
        iNextLinkedObject = aNextLinkedObject;
    }

    Object getCurrentObject() {
        return iCurrentObject;
    }

    LinkedObject getNextLinkedObject() {
        return iNextLinkedObject;
    }
}

public class BlockingQueue {
    private LinkedObject iLinkedListContainer = null;
    private Object iQueueLock = new Object();
    private int iBlockedThreadCount = 0;

    public void appendObject(Object aNewObject) {
        synchronized(iQueueLock) {
            iLinkedListContainer = new iLinkedListContainer(aNewObject, iLinkedListContainer);
            if(iBlockedThreadCount > 0) {
                iQueueLock.notify();//one at a time because we only appended one object
            }
        } //synchonized(iQueueLock)
    }

    public Object getFirstObject() throws InterruptedException {
        Object result = null;
        synchronized(iQueueLock) {
            if(null == iLinkedListContainer) {
                ++iBlockedThreadCount;
                try {
                    iQueueLock.wait();
                    --iBlockedThreadCount; // instead of having a "finally" statement
                } catch (InterruptedException iex) {
                    --iBlockedThreadCount;
                    throw iex;
                }
            }
            result = iLinkedListcontainer.getCurrentObject();
            iLinkedListContainer = iLinkedListContainer.getNextLinkedObject();
            if((iBlockedThreadCount > 0)  && (null != iLinkedListContainer )) {
                iQueueLock.notify();
            }
        }//synchronized(iQueueLock)
        return result;
    }

}

我认为,如果您尝试在同步块中放置更少的代码,则该类将不再正确。

于 2012-04-26T22:35:05.500 回答
-2

这种方法似乎存在一些问题。即使队列中有元素,您也可能会遇到消费者错过通知并在队列中等待的情况。按时间顺序考虑以下序列

T1 - Consumer 获取 queueLock,然后调用 wait。Wait 将释放锁并导致线程等待通知

T2 - 一个生产者获取 queueLock 并将一个元素添加到队列并调用 notify

T3 - 通知消费者线程并尝试获取 queueLock 但由于另一个生产者同时出现而失败。(来自 notify java doc - 被唤醒的线程将以通常的方式与可能积极竞争以同步该对象的任何其他线程竞争;例如,被唤醒的线程在成为下一个要锁定的线程时没有可靠的特权或劣势这个对象。)

T4 - 第二个生产者现在添加另一个元素并调用 notify。当消费者在 queueLock 上等待时,此通知会丢失。

因此,从理论上讲,消费者可能会饿死(永远卡在试图获取 queueLock),您也可能会遇到内存问题,多个生产者将元素添加到队列中,而这些元素没有被读取并从队列中删除。

我建议的一些更改如下 -

  • 保持可以添加到队列中的项目数的上限。
  • 确保消费者始终阅读所有元素。是一个程序,它显示了如何对生产者 - 消费者问题进行编码。
于 2012-04-26T09:53:47.480 回答