为什么 java 中获取信号量类的 Uninterruptibly() 方法没有按预期工作?



我有两个Java文件:

检查.java

import java.util.concurrent.Semaphore;
class Check
{
public static void main(String[] args)
{
Semaphore s = new Semaphore(1);
MyThread t1 = new MyThread("t1",s);
MyThread t2 = new MyThread("t2",s);
t1.start();
t2.start();
t2.interrupt();
}
}

我的线程.java

import java.util.concurrent.Semaphore;
class MyThread extends Thread
{
Semaphore s;
MyThread(String name, Semaphore s)
{
super(name);
this.s=s;
}
public void run()
{
try
{
s.acquireUninterruptibly();
for(int i=0; i<5; i++)
{
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"-"+i);
}
s.release();
}
catch(InterruptedException e){}
}
}

如果我注释掉语句"t2.interrupt((",那么两个线程都执行正常。但是如果我不评论该语句,则线程 t2 根本不执行。根据我对 acquireUninterruptibly(( 方法的理解,线程 t2 即使在中断后也应该继续等待许可。那为什么线程t2在得到中断后停止工作呢?

首先,您不应该捕获异常并忽略它们。此外,不应以某种方式放置异常处理程序,以免代码在异常情况下错过关键release()调用。通常,最小化受保护的代码区域有助于跟踪问题。

当您将代码更改为

Semaphore s = new Semaphore(1);
Runnable r = () -> {
System.out.println(Thread.currentThread().getName()+" - before acquire");
s.acquireUninterruptibly();
System.out.println(Thread.currentThread().getName()+" - acquired");
for(int i=0; i<5; i++) {
try {
Thread.sleep(500);
}
catch(InterruptedException e){
System.out.println(Thread.currentThread().getName()+" - "+e);
}
System.out.println(Thread.currentThread().getName()+" - "+i);
}
s.release();
};
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");
t1.start();
t2.start();
t2.interrupt();

你会得到

t1 - before acquire
t2 - before acquire
t1 - acquired
t1 - 0
t1 - 1
t1 - 2
t1 - 3
t1 - 4
t2 - acquired
t2 - java.lang.InterruptedException: sleep interrupted
t2 - 0
t2 - 1
t2 - 2
t2 - 3
t2 - 4

在大多数情况下。这不能保证,因为t2可能首先获取信号量。

对于典型的计时,t1首先获取,t2在执行acquireUninterruptibly()之前或期间被中断。但是t2是在调用acquireUninterruptibly()之前、中间还是之后中断并不重要,因为此方法会忽略中断。因此,⟨中断⟩状态将一直持续到线程执行第一个操作为止。

在此示例中,这是第一个sleep调用。在您的代码中,这会终止线程,因为您的try … catch语句涵盖了整个代码。在我的示例中,它仅涵盖一个sleep调用,并且由于此调用将在通过InterruptedException报告时重置⟨中断⟩状态,因此后续调用将在捕获异常后按预期休眠。

您也可以自己检查和清除状态,例如

Semaphore s = new Semaphore(1);
Runnable r = () -> {
System.out.println(Thread.currentThread().getName()+" - before acquire");
s.acquireUninterruptibly();
System.out.println(Thread.currentThread().getName()+" - acquired");
if(Thread.interrupted())
System.out.println(Thread.currentThread().getName()
+" - interruption before or during acquire");
for(int i=0; i<5; i++) {
try {
Thread.sleep(500);
}
catch(InterruptedException e){
System.out.println(Thread.currentThread().getName()+" - "+e);
}
System.out.println(Thread.currentThread().getName()+" - "+i);
}
s.release();
};
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");
t1.start();
t2.start();
t2.interrupt();

这可能导致

t1 - before acquire
t2 - before acquire
t1 - acquired
t1 - 0
t1 - 1
t1 - 2
t1 - 3
t1 - 4
t2 - acquired
t2 - interruption before or during acquire
t2 - 0
t2 - 1
t2 - 2
t2 - 3
t2 - 4

如前所述,不能保证。有时,它会打印

t2 - before acquire
t1 - before acquire
t2 - acquired
t2 - interruption before or during acquire
t2 - 0
t2 - 1
t2 - 2
t2 - 3
t2 - 4
t1 - acquired
t1 - 0
t1 - 1
t1 - 2
t1 - 3
t1 - 4

相反。

Semaphore.acquireUninterruptibly()的文档进行比较

如果当前线程在等待许可证时

中断,则它将继续等待,但与未发生中断时为线程分配许可证的时间相比,线程获得许可证的时间可能会发生变化。当线程从此方法返回时,将设置其中断状态。

最后一句话是关键点。当方法被中断时,它会继续等待并正常返回,但线程的"中断状态将被设置",这将使下一个中断敏感操作对其做出反应。

相比之下,Thread.sleep

抛出:

...
InterruptedException- 如果任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态

您的第二个线程实际上开始工作,但由于您忽略了InterruptedException(它的空捕获块(,因此您无法检测到您的interrupt()调用是在线程 2 处于睡眠方法时执行的。如果中断,该方法会抛出InterruptdeException。由于该异常,您的线程 2 退出 for 循环而不打印任何内容。

这是我在 catch 块中添加e.printStackTrace()后看到的。

T1-0

T1-1

T1-2

T1-3

T1-4

java.lang.InterruptedException: sleep Interrupted at java.lang.Thread.sleep(Native Method( at MyThread.run(Parser.java:31(

最新更新