我正在尝试用Java 11实现一个简单的生产者/消费者系统。基本上,我每个人都有两个线程,外加一个全局队列,如下所示:
pushes
将其作为队列中的作业(以queue.size
递增)peeks
进入队列。如果有工作(job ! = null
),则在某处提交HTTP请求,并在成功接收后,从队列中轮询该请求(queue.size()
递减)。骨架如下:
主类:
public class Manager
{
private Consumer consumer;
private Producer producer;
Queue queue;
public static void main (String args[])
{
consumer = new Consumer();
producer = new Producer();
}
}
生产者类别:
public class Producer implements Runnable
{
public Producer()
{
Thread producer = new Thread(this);
producer.start();
}
public void run()
{
//HTTP server starts, listens, and adds to the queue upon receiving a Job
server.start();
Manager.queue.add(new Job());
}
}
消费类:
public class Consumer implements Runnable
{
public Consumer()
{
Thread consumer = new Thread(this);
consumer.start();
}
public void run()
{
// Thread.sleep(1);
while(true)
{
//get an object off the queue
Job job= Manager.queue.peek();
//do some stuff with the object
}
}
}
Producer
和queue
都可以正常工作。但是问题出在Consumer
上。上面的使用者代码(带有while(true)
循环)不会窥视该项目。但是,当我在Thread.sleep(x)
循环之前添加while(true)
时,即使x=1 ms
仍然有效,并且可以成功捕获该项目。
出什么问题了?从理论上讲,while(true)
循环应该不是问题!为什么看不到和peek
物品?!
答案 0 :(得分:1)
问题的原因:队列之间的读写不同步。
这里发生的是,在不同CPU内核上运行的两个线程都使用它们自己的队列副本,因此生产者可能正在添加内容,并且这些更改甚至可能传播到RAM中,但是使用者从未检查任何东西在RAM中,由于它拥有该队列的缓存副本,因此女巫保持为空。
Thread.sleep()
之所以行之有效,是因为唤醒时,线程必须从RAM那里获取所有可能的内容。
正确的方法是仅在同步队列时访问队列,如下所示:
在制作人中:
synchronized(Manager.queue) {
Manager.queue.add(new Job());
}
和消费者:
boolean continue = true;
while (continue) {
synchronized(Manager.queue) {
Job job=Manager.queue.pop();
}
}
最后一点:while (true)
的效率极低,您可以使用Object.wait()
和Object.notify()
在制作人中:
synchronized(Manager.queue) {
Manager.queue.add(new Job());
Manager.queue.notify();
}
和消费者:
boolean continue = true;
while (continue) {
synchronized(Manager.queue) {
while (Manager.queue.peek() == null) {
Manager.queue.wait();
}
Job job=Manager.queue.pop();
}
}
答案 1 :(得分:1)
PriorityQueue
是不是线程安全的,而PriorityBlockingQueue
是。只要您没有使用BlockingQueue
接口中定义的任何方法,这两种实现都是可以互换的。只需将PriorityQueue
更改为PriorityBlockingQueue
应该可以解决您的问题。