Прежде всего, вы не должны ловить исключения и игнорировать их. Кроме того, вы не должны размещать обработчики исключений таким образом, чтобы код пропустил критический вызов 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
- если какая-либо нить прервала текущую нить. статус прерывания текущего потока очищается при возникновении этого исключения.