Тупик в Java-коде с Семафором и приобретать (int)
У меня есть следующий код Java:
import java.util.concurrent.*;
class Foo{
static Semaphore s = new Semaphore(1);
public void fun(final char c, final int r){
new Thread(new Runnable(){
public void run(){
try{
s.acquire(r);
System.out.println(c+"_"+r);
s.release(r+1);
} catch(Exception e){ e.printStackTrace(); }
}
}).start();
}
}
class ths{
public static void main(String[]args) throws Exception{
Foo f = new Foo();
f.fun('B',2);
f.fun('F',6);
f.fun('A',1);
f.fun('C',3);
f.fun('D',4);
f.fun('E',5);
}
}
В идеале это должно печатать от A_1 до F_6 в порядке и выходе, но по какой-то причине этого не происходит. Обычно он печатает A_1 и B_2, а затем застревает.
Я не вижу ничего плохого в моем коде. Любые предложения?
Ответы
Ответ 1
Основная проблема заключается в том, что acquire(int permits)
не гарантирует, что все разрешения будут схвачены за один раз. Он мог получить меньше разрешений и затем блокировать, ожидая остальных.
Рассмотрим ваш код. Когда, скажем, три разрешения становятся доступными там, чтобы гарантировать, что они будут переданы нитью C
. Фактически они могут быть переданы нить D
для частичного удовлетворения запроса acquire(4)
, что приводит к тупиковой ситуации.
Если вы измените код так, это устранит проблему для меня:
public void fun(final char c, final int r){
new Thread(new Runnable(){
public void run(){
try{
while (!s.tryAcquire(r, 1, TimeUnit.MILLISECONDS)) {};
System.out.println(c+"_"+r);
s.release(r+1);
} catch(Exception e){ e.printStackTrace(); }
}
}).start();
}
Забастовкa >
(Во-вторых, вышеизложенное также нарушено, так как нет гарантии, что правильная нить когда-либо получит разрешения - она может продолжать попытки и тайминг бесконечно.)
Ответ 2
Semaphore
действительно получает все разрешения сразу, иначе это не будет реальным semaphore. НО: версия Java также имеет внутреннюю очередь ожидания. И поведение этой очереди НЕ служит наилучшим образом пригодным для использования в настоящее время свободными ресурсами, но более или менее собирает разрешения до тех пор, пока не будет разрешен запрос первого в очереди. Но до того, как поток входит в эту очередь, проверка выполняется, если доступные разрешения позволяют потоку вообще не вводить очередь.
Я изменил ваш код, чтобы показать, что поведение в очереди:
import java.util.concurrent.*;
public class SemaphoreTest{
static Semaphore s = new Semaphore(0);
public void fun(final char c, final int r) throws Exception {
new Thread(new Runnable(){
public void run(){
try{
System.out.println("acquire "+r);
s.acquire(r);
System.out.println(c+"_"+r);
} catch(Exception e){ e.printStackTrace(); }
}
}).start();
Thread.sleep(500);
}
public static void main(String[]args) throws Exception{
SemaphoreTest f = new SemaphoreTest();
f.fun('B',2);
f.fun('F',6);
f.fun('A',1);
f.fun('C',3);
f.fun('D',4);
f.fun('E',5);
while(s.hasQueuedThreads()){
Thread.sleep(1000);
System.out.println("release "+1+", available "+(s.availablePermits()+1));
s.release(1);
}
}
}
В основном были сделаны следующие изменения:
- Начните с 0 разрешений - пусть кто-нибудь сначала войдет в очередь.
- "Определите" порядок очередей, задав каждому потоку 500 мс после
Thread.start
.
- Каждый поток вызывает
acquire
, но не release
.
- Основной поток будет медленно сменять семафор одним разрешением после другого.
Это даст этот результат детерминистически:
acquire 2
acquire 6
acquire 1
acquire 3
acquire 4
acquire 5
release 1, available 1
release 1, available 2
B_2
release 1, available 1
release 1, available 2
release 1, available 3
release 1, available 4
release 1, available 5
release 1, available 6
F_6
release 1, available 1
A_1
release 1, available 1
release 1, available 2
release 1, available 3
C_3
release 1, available 1
release 1, available 2
release 1, available 3
release 1, available 4
D_4
release 1, available 1
release 1, available 2
release 1, available 3
release 1, available 4
release 1, available 5
E_5
release 1, available 1
Это означает: каждый поток проснулся, если
- он находится во главе очереди.
- накоплено достаточное количество разрешений.