我根据this page上的示例创建了一个线程池。 在工作线程中,我们有无限循环,永远不会让线程死掉,而wait()方法调用在没有工作时暂停线程:
while (true) {
synchronized(queue) {
loop:while (queue.isEmpty()) { // labled the loop so we can return here
try
{
queue.wait();
if(queue.isEmpty()) // check the condition predicate again
continue loop;
}
catch (InterruptedException ignored)
{
}
}
r = (Runnable) queue.removeFirst();
}
// If we don't catch RuntimeException,
// the pool could leak threads
try {
r.run();
}
catch (RuntimeException e) {
// You might want to log something here
}
事实是,如果队列为空,r = (Runnable) queue.removeFirst();
可以抛出一个NoSuchElementException
,这是一个RuntimeException。当在该行上抛出此类异常时,持有互斥锁的当前线程将死亡,并且池会泄漏线程。当线程死亡时,似乎会释放互斥锁。
但是,如果您不使用默认的synchronized
关键字来同步队列,请使用ReentrantLock
锁定并Condition
进行信令和等待,当前持有互斥锁的线程在意外中断时似乎没有释放锁。
因此,在我的情况下,当我在“线程”选项卡下使用JVisualVM进行检查时,我可以看到AWT-EventQueue-0
线程正在为Thread-1
释放互斥锁。但Thread-1在运行任务的过程中死亡,并意外终止(RuntumeException
)并且互斥锁似乎没有被释放。
我的问题:
1)如果持有它的线程意外终止,是不是ReentrantLocks被释放了?
2)上面的代码段中while (queue.isEmpty()) {
和if (queue.isEmpty()) {
之间是否有任何区别?我看不出任何差异,因为线程会在两种情况下都等待。但我认为使用if
时行为不同(如果多个线程可能会影响队列)。
修改
Java Concurrency in Practice声明:
出于所有这些原因,当你从等待中醒来时,你必须这样做 再次测试条件谓词,然后再回去等待 (或失败)如果还不是真的。既然你可以醒来 没有你的条件谓词是真的,反复向上,你 因此必须始终在循环内调用wait,进行测试 每次迭代中的条件谓词。
在上面的代码中查看我的编辑,现在代码应该正如Java Concurrency in Practice中所述。
答案 0 :(得分:4)
1)如果持有它的线程预期终止,ReentrantLocks是否会被释放?
仅在您明确调用Lock#unlock()时锁定释放。这就是为什么建议在finally块中调用Lock#unlock()以防止应用程序出现死锁。
2)while(queue.isEmpty()){和if之间是否有任何区别? (queue.isEmpty()){在上面的代码片段中?我什么也看不见 因为线程在两种情况下都会等待。但我认为 使用if时的行为不同(比如多个线程可以 影响队列。)
在特定情况下没有大的差异。但是使用while
保证在应用程序中使用assert,当Queue为空时,不会调用removeFirst()
。
此外,使用while而不是if的PROS是spurious wakeups。
注释: 如果您不仅要为教育实施此架构,请考虑使用BlockingQueue。 java.util.concurrent库解决了许多多线程问题,在大多数情况下,您可以基于java.util.concurrent的高级抽象而不是使用低级技术(如wait()/ notify())来构建应用程序。
答案 1 :(得分:1)
你的代码看起来太复杂了 - 我只想写:
while (true) {
synchronized(queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException ignored) {
//don't ignore me please
//you probably should exit the loop and return here...
}
}
r = queue.removeFirst(); //Why use a cast? Use generics instead.
}
}
queue.removeFirst()
可能抛出NoSuchElementException的唯一情况是它是否同时被修改,如果所有对队列的访问都是在synchronized块中进行的话,这是不可能的。
因此,无需在显示器上按住锁定即可找到您访问队列的位置,您将解决问题。
你必须在一个循环中调用wait
的原因是等待可能是虚假唤醒(即因为等待唤醒并不意味着你的情况已经变为真,所以你需要再次测试它。)
作为旁注,如果您使用了BlockingQueue,则不必担心这些低级别的详细信息,您可以简单地写一下:
while(true) {
Runnable r = queue.take(); //blocks until queue is not empty
}
答案 2 :(得分:0)
java.util.concurrent
中的内容为您提供了更多灵活性,例如定时等待和尝试锁定/获取方法。此外,synchronized会为您提供代码块,与lock.lock()/ unlock()对不同。当没有争用时,它也往往更有效率。
无论如何,当使用并发时,一定要考虑java.util.concurrent
,因为已经解决了许多问题。