所以我模拟了我的制作人消费者问题,我有下面的代码。我的问题是:如果消费者处于不变的状态(消极的话),消费者会如何停止。
在下面的代码中,我添加了
if (queue.peek()==null)
Thread.currentThread().interrupt();
在这个例子中很好用。但在我的真实世界设计中,这不起作用(有时生产者需要更长的时间来“放置”数据,因此消费者抛出的异常是不正确的。一般来说,我知道我可以放一个'毒'数据比如Object是XYZ,我可以在消费者中检查它。但这种毒药会使代码看起来很糟糕。想知道是否有人采用不同的方法。
public class ConsumerThread implements Runnable
{
private BlockingQueue<Integer> queue;
private String name;
private boolean isFirstTimeConsuming = true;
public ConsumerThread(String name, BlockingQueue<Integer> queue)
{
this.queue=queue;
this.name=name;
}
@Override
public void run()
{
try
{
while (true)
{
if (isFirstTimeConsuming)
{
System.out.println(name+" is initilizing...");
Thread.sleep(4000);
isFirstTimeConsuming=false;
}
try{
if (queue.peek()==null)
Thread.currentThread().interrupt();
Integer data = queue.take();
System.out.println(name+" consumed ------->"+data);
Thread.sleep(70);
}catch(InterruptedException ie)
{
System.out.println("InterruptedException!!!!");
break;
}
}
System.out.println("Comsumer " + this.name + " finished its job; terminating.");
}catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
答案 0 :(得分:7)
答:根本不能保证只因为peek
返回null
,生产者已停止生产。如果生产者放慢速度怎么办?现在,消费者退出,生产者继续生产。所以'偷看' - &gt; “破解”的想法基本上失败了。
B:如果出现以下情况,则设置来自消费者的'完成/运行'标志并在生产者中读取它也会失败:
相反的情况也可能发生,并且一个数据包被遗漏掉了。
然后,为了解决这个问题,你需要在'BlockingQueue'之上与互斥锁进行额外的同步。
<强> C:强> 在这种情况下,我发现'Rosetta Code'是决定什么是好习惯的良好来源:
http://rosettacode.org/wiki/Synchronous_concurrency#Java
生产者和消费者必须就表示输入结束的对象(或对象中的属性)达成一致。然后生产者在最后一个数据包中设置该属性,并且消费者停止消费它。即你在问题中提到的“毒药”。
在上面的Rosetta Code示例中,这个'对象'只是一个空的String
,称为'EOF':
final String EOF = new String();
// Producer
while ((line = br.readLine()) != null)
queue.put(line);
br.close();
// signal end of input
queue.put(EOF);
// Consumer
while (true)
{
try
{
String line = queue.take();
// Reference equality
if (line == EOF)
break;
System.out.println(line);
linesWrote++;
}
catch (InterruptedException ie)
{
}
}
答案 1 :(得分:3)
不要在线程上使用中断,而是在不再需要时断开循环:
if (queue.peek()==null)
break;
或者您也可以使用变量标记关闭操作挂起,然后在以下情况后断开循环并关闭循环:
if (queue.peek()==null)
closing = true;
//Do further operations ...
if(closing)
break;
答案 2 :(得分:0)
在现实世界中,大多数消息传递都带有某种类型的标头,用于定义消息类型/子类型或可能是不同的对象。
您可以创建一个命令和控制对象或消息类型,告诉线程在获取消息时执行某些操作(如关闭,重新加载表,添加新的侦听器等)。
这样,您可以说命令和控制线程只是将消息发送到正常的消息流中。您可以让CNC线程与大型系统中的操作终端通信等。
答案 3 :(得分:0)
如果您的队列在您希望消费者终止之前可以清空,那么您需要一个标志来告诉线程何时停止。添加setter方法,以便生产者可以告诉使用者关闭。然后修改代码,而不是:
if (queue.isEmpty())
break;
让代码检查
if (!run)
{
break;
}
else if (queue.isEmpty())
{
Thread.sleep(200);
continue;
}
答案 4 :(得分:0)
您可以将这种类型安全模式与毒药一起使用:
public sealed interface BaseMessage {
final class ValidMessage<T> implements BaseMessage {
@Nonnull
private final T value;
public ValidMessage(@Nonnull T value) {
this.value = value;
}
@Nonnull
public T getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ValidMessage<?> that = (ValidMessage<?>) o;
return value.equals(that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "ValidMessage{value=%s}".formatted(value);
}
}
final class PoisonedMessage implements BaseMessage {
public static final PoisonedMessage INSTANCE = new PoisonedMessage();
private PoisonedMessage() {
}
@Override
public String toString() {
return "PoisonedMessage{}";
}
}
}
public class Producer implements Callable<Void> {
@Nonnull
private final BlockingQueue<BaseMessage> messages;
Producer(@Nonnull BlockingQueue<BaseMessage> messages) {
this.messages = messages;
}
@Override
public Void call() throws Exception {
messages.put(new BaseMessage.ValidMessage<>(1));
messages.put(new BaseMessage.ValidMessage<>(2));
messages.put(new BaseMessage.ValidMessage<>(3));
messages.put(BaseMessage.PoisonedMessage.INSTANCE);
return null;
}
}
public class Consumer implements Callable<Void> {
@Nonnull
private final BlockingQueue<BaseMessage> messages;
private final int maxPoisons;
public Consumer(@Nonnull BlockingQueue<BaseMessage> messages, int maxPoisons) {
this.messages = messages;
this.maxPoisons = maxPoisons;
}
@Override
public Void call() throws Exception {
int poisonsReceived = 0;
while (poisonsReceived < maxPoisons && !Thread.currentThread().isInterrupted()) {
BaseMessage message = messages.take();
if (message instanceof BaseMessage.ValidMessage<?> vm) {
Integer value = (Integer) vm.getValue();
System.out.println(value);
} else if (message instanceof BaseMessage.PoisonedMessage) {
++poisonsReceived;
} else {
throw new IllegalArgumentException("Invalid BaseMessage type: " + message);
}
}
return null;
}
}