Подтвердить что ты не робот

Тупик в 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, а затем застревает.

Я не вижу ничего плохого в моем коде. Любые предложения?

4b9b3361

Ответ 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

Это означает: каждый поток проснулся, если

  • он находится во главе очереди.
  • накоплено достаточное количество разрешений.