Java生产者消费者ArrayBlockingQueue在take()上发生死锁
在我的应用程序中有两个阶段,一个是下载一些大数据,另一个是操纵数据。 因此,我创建了两个实现runnable的类:ImageDownloader和ImageManipulator,它们共享一个downloadedBlockingQueue:Java生产者消费者ArrayBlockingQueue在take()上发生死锁,java,multithreading,blockingqueue,Java,Multithreading,Blockingqueue,在我的应用程序中有两个阶段,一个是下载一些大数据,另一个是操纵数据。 因此,我创建了两个实现runnable的类:ImageDownloader和ImageManipulator,它们共享一个downloadedBlockingQueue: public class ImageDownloader implements Runnable { private ArrayBlockingQueue<ImageBean> downloadedImagesB
public class ImageDownloader implements Runnable {
private ArrayBlockingQueue<ImageBean> downloadedImagesBlockingQueue;
private ArrayBlockingQueue<String> imgUrlsBlockingQueue;
public ImageDownloader(ArrayBlockingQueue<String> imgUrlsBlockingQueue, ArrayBlockingQueue<ImageBean> downloadedImagesBlockingQueue) {
this.downloadedImagesBlockingQueue = downloadedImagesBlockingQueue;
this.imgUrlsBlockingQueue = imgUrlsBlockingQueue;
}
@Override
public void run() {
while (!this.imgUrlsBlockingQueue.isEmpty()) {
try {
String imgUrl = this.imgUrlsBlockingQueue.take();
ImageBean imageBean = doYourThing(imgUrl);
this.downloadedImagesBlockingQueue.add(imageBean);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ImageManipulator implements Runnable {
private ArrayBlockingQueue<ImageBean> downloadedImagesBlockingQueue;
private AtomicInteger capacity;
public ImageManipulator(ArrayBlockingQueue<ImageBean> downloadedImagesBlockingQueue,
AtomicInteger capacity) {
this.downloadedImagesBlockingQueue = downloadedImagesBlockingQueue;
this.capacity = capacity;
}
@Override
public void run() {
while (capacity.get() > 0) {
try {
ImageBean imageBean = downloadedImagesBlockingQueue.take(); // <- HERE I GET THE DEADLOCK
capacity.decrementAndGet();
} catch (InterruptedException e) {
e.printStackTrace();
}
// ....
}
}
}
public class Main {
public static void main(String[] args) {
String[] imageUrls = new String[]{"url1", "url2"};
int capacity = imageUrls.length;
ArrayBlockingQueue<String> imgUrlsBlockingQueue = initImgUrlsBlockingQueue(imageUrls, capacity);
ArrayBlockingQueue<ImageBean> downloadedImagesBlockingQueue = new ArrayBlockingQueue<>(capacity);
ExecutorService downloaderExecutor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
Runnable worker = new ImageDownloader(imgUrlsBlockingQueue, downloadedImagesBlockingQueue);
downloaderExecutor.execute(worker);
}
downloaderExecutor.shutdown();
ExecutorService manipulatorExecutor = Executors.newFixedThreadPool(3);
AtomicInteger manipulatorCapacity = new AtomicInteger(capacity);
for (int i = 0; i < 3; i++) {
Runnable worker = new ImageManipulator(downloadedImagesBlockingQueue, manipulatorCapacity);
manipulatorExecutor.execute(worker);
}
manipulatorExecutor.shutdown();
while (!downloaderExecutor.isTerminated() && !manipulatorExecutor.isTerminated()) {
}
}
}
公共类ImageDownloader实现可运行{
私有阵列锁定队列下载图像锁定队列;
专用ArrayBlockingQueue imgUrlsBlockingQueue;
公共图像下载程序(ArrayBlockingQueue imgUrlsBlockingQueue,ArrayBlockingQueue downloadedImagesBlockingQueue){
this.downloadeImagesBlockingQueue=downloadeImagesBlockingQueue;
this.imgUrlsBlockingQueue=imgUrlsBlockingQueue;
}
@凌驾
公开募捐{
而(!this.imgUrlsBlockingQueue.isEmpty()){
试一试{
字符串imgUrl=this.imgUrlsBlockingQueue.take();
ImageBean ImageBean=doYourThing(imgUrl);
this.downloadeImagesBlockingQueue.add(imageBean);
}捕捉(中断异常e){
e、 printStackTrace();
}
}
}
}
公共类ImageManipulator实现可运行{
私有阵列锁定队列下载图像锁定队列;
私有原子能力;
公共图像操纵器(ArrayBlockingQueue下载图像锁定队列,
原子(整数容量){
this.downloadeImagesBlockingQueue=downloadeImagesBlockingQueue;
这个。容量=容量;
}
@凌驾
公开募捐{
while(capacity.get()>0){
试一试{
ImageBean ImageBean=downloadeImagesBlockingQueue.take();//您不需要消费者中的容量。它现在在多个线程中读取和更新,这会导致同步问题
initImgUrlsBlockingQueue
使用容量
url项目数创建url阻止队列。(对吗?)
ImageDownloader
使用imgUrlsBlockingQueue
并生成图像,它在下载所有URL时终止,或者,如果capacity
表示由于可能出现故障而应下载的图像数,它在添加capacity
图像数时终止
在ImageDownloader
终止之前,它会在downloadedImagesBlockingQueue
中添加一个标记,例如,一个空元素,一个静态最终ImageBean静态最终ImageBean marker=new ImageBean()
所有ImageManipulator
都使用以下构造来清空队列,当它看到null元素时,它会再次将其添加到队列并终止
// use identity comparison
while ((imageBean = downloadedImagesBlockingQueue.take()) != marker) {
// process image
}
downloadedImagesBlockingQueue.add(marker);
请注意,BlockingQueue
承诺其方法将其称为原子,但是,如果您首先检查其容量,并根据容量使用一个元素,则操作组将不会是原子的。您可以做几件事来防止死锁:
- 使用具有容量的
LinkedBlockingQueue
- 使用
offer
添加到不阻塞的队列中
- 使用
drainTo
或poll
从队列中提取未阻塞的项目
您可能还需要考虑一些技巧:
- 使用
线程池:
final ExecutorService ExecutorService=Executors.newFixedThreadPool(4);
- 如果使用固定大小的
线程池
,则可以在将数据添加到与线程池
大小对应的队列中后添加s,并在轮询时检查它
使用线程池
非常简单:
final ExecutorService executorService = Executors.newFixedThreadPool(4);
final Future<?> result = executorService.submit(new Runnable() {
@Override
public void run() {
}
});
final ExecutorService ExecutorService=Executors.newFixedThreadPool(4);
最终的未来结果=executorService.submit(新的Runnable(){
@凌驾
公开募捐{
}
});
还有一个鲜为人知的ExecutorCompletionService
,它对整个过程进行了抽象。更多信息。您的问题是,您试图使用计数器跟踪队列元素,而不是编写需要原子化的操作。您正在执行检查、取数、减量。这允许队列大小和计数器去同步并且线程会永远阻塞。最好编写一个“可关闭”的同步原语,这样就不必保留关联的计数器。但是,一个快速解决方法是更改它,这样就可以获得并原子地递减计数器:
while (capacity.getAndDecrement() > 0) {
try {
ImageBean imageBean = downloadedImagesBlockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
在这种情况下,如果有3个线程,队列中只剩下一个元素,那么只有一个线程将自动减少计数器,并查看它是否可以在不阻塞的情况下运行。其他两个线程都将看到0或好的,我使用了一些建议的功能,但这是我的完整解决方案,一个不忙着等待和不等待的线程直到下载程序通知它
public ImageManipulator(LinkedBlockingQueue<ImageBean> downloadedImagesBlockingQueue,
LinkedBlockingQueue<ImageBean> manipulatedImagesBlockingQueue,
AtomicInteger capacity,
ManipulatedData manipulatedData,
ReentrantLock downloaderReentrantLock,
ReentrantLock manipulatorReentrantLock,
Condition downloaderNotFull,
Condition manipulatorNotFull) {
this.downloadedImagesBlockingQueue = downloadedImagesBlockingQueue;
this.manipulatedImagesBlockingQueue = manipulatedImagesBlockingQueue;
this.capacity = capacity;
this.downloaderReentrantLock = downloaderReentrantLock;
this.manipulatorReentrantLock = manipulatorReentrantLock;
this.downloaderNotFull = downloaderNotFull;
this.manipulatorNotFull = manipulatorNotFull;
this.manipulatedData = manipulatedData;
}
@Override
public void run() {
while (capacity.get() > 0) {
downloaderReentrantLock.lock();
if (capacity.get() > 0) { //checks if the value is updated.
ImageBean imageBean = downloadedImagesBlockingQueue.poll();
if (imageBean != null) { // will be null if no downloader finished is work (successfully downloaded or not)
capacity.decrementAndGet();
if (capacity.get() == 0) { //signal all the manipulators to wake up and stop waiting for downloaded images.
downloaderNotFull.signalAll();
}
downloaderReentrantLock.unlock();
if (imageBean.getOriginalImage() != null) { // the downloader will set it null iff it failes to download it.
// business logic
}
manipulatedImagesBlockingQueue.add(imageBean);
signalAllPersisters(); // signal the persisters (which has the same lock/unlock as this manipulator.
} else {
try {
downloaderNotFull.await(); //manipulator will wait for downloaded image - downloader will signalAllManipulators (same as signalAllPersisters() here) when an imageBean will be inserted to queue.
downloaderReentrantLock.unlock();
} catch (InterruptedException e) {
logger.log(Level.ERROR, e.getMessage(), e);
}
}
}
}
logger.log(Level.INFO, "Manipulator: " + Thread.currentThread().getId() + " Ended Gracefully");
}
private void signalAllPersisters() {
manipulatorReentrantLock.lock();
manipulatorNotFull.signalAll();
manipulatorReentrantLock.unlock();
}
公共图像操纵器(LinkedBlockingQueue下载图像锁定队列,
LinkedBlockingQueue操纵ImagesBlockingQueue,
原子整数容量,
操纵数据操纵数据,
ReentrantLock下载程序ReentrantLock,
可重入锁定操纵器可重入锁定,
条件下载未满,
条件(或未满){
this.downloadeImagesBlockingQueue=downloadeImagesBlockingQueue;
this.manipedimagesblockingqueue=manipedimagesblockingqueue;
这个。容量=容量;
this.downloaderReentrantLock=downloaderReentrantLock;
this.manufactorreentrantlock=manufactorreentrantlock;
this.downloaderNotFull=downloaderNotFull;
this.manufactornotfull=manipul