为什么BlockingQueue.take()没有释放线程?

时间:2011-07-21 00:14:01

标签: java multithreading concurrency

在这个简单的短程序中,您会注意到该程序永远挂起,因为take()不会释放该线程。根据我的理解,即使任务本身在take()上被阻塞,take()也会释放线程。

编辑:

这有效(感谢大家修复自动装箱):

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducersConsumers {
    private static int THREAD_COUNT = 5;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        final ExecutorService executorPool = Executors.newFixedThreadPool(THREAD_COUNT);
        final LinkedBlockingQueue<Long> queue = new LinkedBlockingQueue<Long>();

        Collection<Future<Long>> collection = new ArrayList<Future<Long>>();


        // producer:
        for (int i = 0; i < 20; i++) {
            collection.add(executorPool.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    for (int i = 100; i >= 0; i--) {
                        queue.put((long) i);
                    }
                    return -1L;
                }
            }));
        }

        // consumer:
        for (int i = 0; i < 20; i++) {
            collection.add(executorPool.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    while (true) {
                        Long item = queue.take();
                        if (item.intValue() == 0) {
                            break;
                        }
                    }
                    return 1L;
                }
            }));
        }

        long sum = 0;
        for (Future<Long> item : collection) {
            sum += item.get();
        }

        executorPool.shutdown();
        System.out.println("sum = " + sum);
    }
}

但如果您交换生产者和消费者调用,它将挂起:

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducersConsumers {
    private static int THREAD_COUNT = 5;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        final ExecutorService executorPool = Executors.newFixedThreadPool(THREAD_COUNT);
        final LinkedBlockingQueue<Long> queue = new LinkedBlockingQueue<Long>();

        Collection<Future<Long>> collection = new ArrayList<Future<Long>>();


        // consumer:
        for (int i = 0; i < 20; i++) {
            collection.add(executorPool.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    while (true) {
                        Long item = queue.take();
                        if (item.intValue() == 0) {
                            break;
                        }
                    }
                    return 1L;
                }
            }));
        }

        // producer:
        for (int i = 0; i < 20; i++) {
            collection.add(executorPool.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    for (int i = 100; i >= 0; i--) {
                        queue.put((long) i);
                    }
                    return -1L;
                }
            }));
        }

        long sum = 0;
        for (Future<Long> item : collection) {
            sum += item.get();
        }

        executorPool.shutdown();
        System.out.println("sum = " + sum);
    }
}

据我了解,生产者和消费者的订单无关紧要。换句话说,有一个任务和线程的概念。线程独立于代码程序,而任务与某个程序相关联。因此,在我的示例中,当JVM分配线程以执行可调用任务时,如果首先实例化消费者,则该任务将在take()上阻塞。一旦JVM发现任务被阻止,它将释放该线程(或者我理解它但它没有释放它)并将其放回工作线程池以准备处理可运行的任务(在这种情况下是制片人)。因此,在实例化所有Callable的最后,应该有40个任务但只有5个线程;其中20个任务被阻止,5个任务应该运行,15个应该等待(运行)。

4 个答案:

答案 0 :(得分:2)

我认为你误解了线程和线程池的工作方式。线程池通常有一个工作项队列,其中包含要处理的项目(在您的情况下为Callable<>)。

它还包含(最多)线程数(在您的情况下为5),可以处理这些项目。

活动线程的生命周期由它执行的代码定义 - 通常是一种方法。线程在开始执行方法时变为“活动”,并在返回时结束。如果方法阻塞等待一些信号,那并不意味着线程可以消失并执行其他方法 - 这不是线程的工作方式。相反,线程将被阻塞,直到它可以继续执行并启用其他线程运行。

由线程池线程运行的方法通常如下所示:

void threadloop()
{
    while (!quit)
    {
        Callable<T> item = null;
        synchronized (workQueue)
        {
            if (workQueue.Count == 0)
                workQueue.wait();

            // we could have been woken up for some other reason so check again
            if (workQueue.Count > 0)
                item = workQueue.pop();
        }
        if (item != null)
             item.Call();
    }
}

这或多或少是伪代码(我不是Java开发人员),但它应该显示这个概念。现在item.Call()执行池用户提供的方法。如果该方法阻止,那么会发生什么?好吧 - 线程将在执行item.Call()时被阻止,直到该方法再次唤醒。它不能随便离开并随意执行其他代码。

答案 1 :(得分:1)

我不知道release thread到底是什么意思,但是一旦阻止take(),调用线程就会被阻止,而不会回到池中。

答案 2 :(得分:1)

来自javadoc

检索并删除此队列的头部,等待此队列中没有元素。

它会等待:你在main中运行,所以它会留在那里。

编辑:更正:阻塞仍然发生(在线程池线程中,而不是在main中)。没有让步:在take调用中阻塞了20个线程,因此没有put个调用执行,因此Future永远不会完成,因此程序会挂起。

答案 3 :(得分:1)

我认为你误解了BlockingQueue中被“阻止”的内容。

queue.take()的调用阻止调用它的线程,直到队列中有可用的东西为止。这意味着线程将在那里无休止地等待,除非被中断,直到将一个项添加到队列中。

第二个代码示例挂起问题,因为您要添加20个任务以等待项目出现在BlockingQueue中,并且执行程序中只有5个线程 - 因此前五个任务导致所有五个阻止的线程。这个执行者充满了15个其他的消费者任务。

在第二个for循环中添加任务以将项添加到队列会导致20个永远无法执行的任务,因为执行程序中的所有线程都在等待。

所以当你这样说时:

  

根据我的理解,即使任务本身在take()上被阻止,take()也会释放线程。

你有一个误解,因为“线程”的作用与“任务”的作用之间没有区别。任务被阻止时,线程无法“释放” - 它是运行任务的线程。当线程遇到阻塞调用take()时,线程被阻塞,句点。