在这个简单的短程序中,您会注意到该程序永远挂起,因为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个应该等待(运行)。
答案 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()
时,线程被阻塞,句点。