如何正确地使多个线程访问共享缓冲区

时间:2018-07-24 09:39:20

标签: java concurrency deadlock synchronizedcollection

我遇到了生产者/消费者的情况,生产者生产了供消费者访问的域。消费者发送一个https请求,并从页面中获取链接,然后将其提交回生产者。生产者完成时,消费者没有,并且挂在最终域上。我无法确定为什么会这样。

  

我简化了我的问题

主要:

const bodyParser = require('body-parser');
const express = require('express');
const cors = require('cors');
const app = express();

var corsOptions = {
    origin: 'http://example.com',
    optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};

app.use(bodyParser.json());
app.use(cors(corsOptions));


app.route('/api/cats').get((req, res) => {
    res.send({
        cats: [{ name: 'lilly' }, { name: 'lucy' }]
    });
});

app.route('/api/cats').post((req, res) => {
    res.send(201, req.body);
});

app.route('/api/cats/:name').get((req, res) => {
    const requestedCatName = req.params['name'];
    res.send({ name: requestedCatName });
});

app.route('/api/cats/:name').put((req, res) => {
    res.send(200, req.body);
});

app.route('/api/cats/:name').delete((req, res) => {
    res.sendStatus(204);
});

app.listen(8000, () => {
    console.log('Server started');
});

经纪人:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        try
        {
            Broker broker = new Broker();

            ExecutorService threadPool = Executors.newFixedThreadPool(3);


            threadPool.execute(new Consumer(broker));
            threadPool.execute(new Consumer(broker));
            Future producerStatus = threadPool.submit(new Producer(broker));

            // this will wait for the producer to finish its execution.
            producerStatus.get();


            threadPool.shutdown();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }

}

消费者:

public class Broker {
    private BlockingQueue<String> QUEUE = new LinkedBlockingQueue<String>();
    public Boolean continueProducing = Boolean.TRUE;

     public void put(String data) throws InterruptedException
     {
            this.QUEUE.put(data);
     }

     public String get() throws InterruptedException
     {
            //return this.queue.poll(1, TimeUnit.SECONDS);
            return this.QUEUE.take();
     }
}

制作人:

public class Consumer implements Runnable{

    private Broker broker;


    public Consumer(Broker broker) {
        this.broker = broker;
    }

    @Override
    public void run() {


        try {
            String data = broker.get();
            while (broker.continueProducing || data != null)
            {
                Thread.sleep(1000);
                System.out.println("Consumer " + Thread.currentThread().getName() + " processed data from broker: " + data);
                data = broker.get();
            }

            System.out.println("Comsumer " + Thread.currentThread().getName() + " finished its job; terminating.");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }


    }


}

1 个答案:

答案 0 :(得分:2)

更新的答案:

运行我的代码时,使用者卡在data = broker.get()行上。代理正在调用BlockingQueue.take方法。这是此方法的Javadoc(重点是我的):

  

检索并删除此队列的头部,在必要时等待,直到某个元素可用为止

这意味着即使生产者没有生产任何东西,消费者仍将等待生产。

对您来说,一种可能的解决方案是使用“ poison pill”方法。假设您只有一个生产者,那么您的Broker类可能如下所示:

public class Broker {

  private static final String POISON_PILL = "__POISON_PILL__";
  private BlockingQueue<String> queue = new LinkedBlockingQueue<>();

  public void put(String data) {
    queue.add(data);
  }

  public void doneProducing() {
    queue.add(POISON_PILL);
  }

  public String get() throws InterruptedException {
    String result = queue.take();
    if (result.equals(POISON_PILL)) {
      queue.add(POISON_PILL);
      return null;
    } else {
      return result;
    }
  }

}

回答先前的代码:

如果您可以缩小此问题的范围,使其仅包含最少数量的代码即可获得死锁,那就太好了。就目前而言,您正在发布的很多代码都不相关,并且有一些您无关的代码。

此外,您当前的代码还有很多问题。您的toLinkedHashSet方法无法编译。在您的add方法中,即使您的BlockingQueue.put永远不会达到其极限,您仍在调用BlockingQueue方法。您声称要contains花费OO(1)的时间,但是您的代码却花费OO(n)的时间。您还似乎在addAllcontains方法中做了很多不必要的复制。

这里没有足够的信息让我知道问题是什么,但是可能导致您的问题的一件事是您的get方法中。如果使用者线程被中断,则您的get方法将使其自身不中断(这可能不会导致死锁,但看起来像一个)。在Java中,很少会忽略异常。如果您对take方法的调用抛出了InterruptedException,则是有原因的:另一个线程希望当前线程停止。您的get方法应抛出InterruptedException。例如:

public String get() throws InterruptedException {
    return unprocessed.take();
}

如果您确实需要使用get方法不抛出InterruptedException,则可以抛出其他包含InterruptedException的链接异常。如果确实适合在中断时返回"",则可以执行以下操作:

public String get() {
  try {
    return unprocessed.take();
  } catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    return "";
  }
}

通过中断当前线程,您可以确保至少当前线程被标记为已中断,因此可以进行一些处理。但是,如果可能的话,抛出InterruptedException可能是最合适的。

我仍然不明白为什么要为LinkedBlockingQueue创建自己的包装器,而不是仅仅自己使用LinkedBlockingQueue。看来您在LinkedBlockingQueue上添加的所有内容除了减慢速度外什么也没做。