2014-11-25 2 views
0

Я столкнулся с проблемой в рамках мастерской Санта-Клауса, где должно случиться множество вещей до подарков. Одним из таких требований является то, что одиннадцать эльфов успешно производят 100 подарков. Мой главный класс выглядит следующим образом:Как правильно использовать семафоры

public class Main { 

    public volatile int toyList = 100; 

    public int getToyList() { 
     return toyList; 
    } 

    public void setToyList(int toyList) { 
     this.toyList = toyList; 
    } 

    public static void main(String[] args){ 
     Main main = new Main(); 

     ... 

     Elf elfOne = new Elf("Jim", main); 
     Elf elfTwo = new Elf("John", main); 
     Elf elfThree = new Elf("Eamonn", main); 
     Elf elfFour = new Elf("Eoin", main); 
     Elf elfFive = new Elf("Ronan", main); 
     Elf elfSix = new Elf("Seamus", main); 
     Elf elfSeven = new Elf("Rebecca", main); 
     Elf elfEight = new Elf("Orla", main); 
     Elf elfNine = new Elf("Tina", main); 
     Elf elfTen = new Elf("Filly", main); 
     Elf elfEleven = new Elf("Jess", main); 


     ... 

     elfOne.start(); 
     elfTwo.start(); 
     elfThree.start(); 
     elfFour.start(); 
     elfFive.start(); 
     elfSix.start(); 
     elfSeven.start(); 
     elfEight.start(); 
     elfNine.start(); 
     elfTen.start(); 
     elfEleven.start(); 

    } 

и мой Elf класс выглядит следующим образом:

public class Elf extends Thread{ 

    Semaphore semaphore = new Semaphore(11); 
    Random rng = new Random(); 
    String name; 
    Main main; 

    public Elf(String name, Main main){ 
     this.name = name; 
     this.main = main; 
    } 

    public void run(){ 
     while(true){ 
       try { 
        semaphore.acquire(); 
        makeToy(); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } finally { 
        semaphore.release(); 
       } 
     } 
    } 

    private void makeToy() throws InterruptedException{ 
     if (main.getToyList() != 0){ 
      if(rng.nextInt(99) <= 4){ //5% chance to fail 
       System.out.println(name + " failed in making a toy!"); 
      }else{ 
       main.setToyList(main.getToyList() - 1); 
       System.out.println(name + " created a toy! Toys remaining: " + main.getToyList()); 
      } 
     } else { 
      wakeSanta(); 
     } 
    } 

    private void wakeSanta() throws InterruptedException { 

    } 

} 

Выходной образец я получаю:

... 
Eamonn created a toy! Toys remaining: 6 
Jim created a toy! Toys remaining: 7 
Eoin created a toy! Toys remaining: 8 
Jess created a toy! Toys remaining: 9 
Filly created a toy! Toys remaining: 0 

Выход я хотел бы get заключается в том, что он последовательно подсчитывается случайным эльфом от 100 до 0. Является ли это чем-то таким же простым, как получение семафора в неправильном месте или я вообще не понимаю семафоров?

+1

Ресурс вы пытаетесь защитить это внутр. Если ваше задание специально не потребует, чтобы Семафор или список игрушек впоследствии были преобразованы в другую структуру, использование AtomicInteger также выполнило бы эту работу. Без сложности управления семафором. – Ralf

ответ

1

Да, ваш семафор не используется правильно. Экземпляр семафора может предоставить несколько разрешений.

  • Поток может acquire() разрешение - блок до тех пор, пока не будет доступно разрешение, а затем уменьшено количество доступных разрешений.
  • Поток может release() разрешение - увеличить количество доступных разрешений.

В вашем коде каждый поток имеет свой собственный семафор с разрешениями 11. Во-первых, будучи средством синхронизации, семафоры обычно имеют смысл только в том случае, если они разделяются несколькими потоками. Во-вторых, если вы хотите сделать ресурс (в вашем случае toyList) полностью взаимоисключающим, семафор должен предоставить только одно разрешение.

Думайте об этом так: для изготовления игрушек эльфы должны использовать машину. Теперь ваш сценарий требует, чтобы только один эльф мог изготовить игрушку одновременно, что эквивалентно мастерской, имеющей только одну игрушечную машину. Итак - в коде - вам нужно создать эквивалент одной машины: один Semaphore - только с одним разрешением. Итак, давайте положить Semaphore в Main -класса:

public class Main { 

    Semaphore semaphore = new Semaphore(1); 
    // the rest of Main is kept the same 
} 

Теперь эльфы должны получить доступ к игрушечные машины производство, поэтому вместо semaphore.acquire() мы используем main.semaphore.aquire(). Но если машина занята при вызове run() и никогда не была выпущена, первый эльф, который начнет работать, никогда не позволит другому эльфию иметь машину.Так Санта подавлено некоторые правила о том, как машина будет использоваться:

  • Машина не должна быть занята, если эльф попытки сделать игрушку
  • Машина должна освобождаться когда эльф (успешно или нет) закончил делать игрушку

так код run() и makeToy() изменяется следующим образом:

public void run(){ 
    while(true){ // sidenote: I would rearrange the nesting: Wrap the while-loop in the try-catch 
     try { 
      makeToy(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

private void makeToy() throws InterruptedException{ 
    main.semaphore.acquire(); 
    if (main.getToyList() != 0){ 
     if(rng.nextInt(99) <= 4){ //5% chance to fail 
      System.out.println(name + " failed in making a toy!"); 
     }else{ 
      main.setToyList(main.getToyList() - 1); 
      System.out.println(name + " created a toy! Toys remaining: " + main.getToyList()); 
     } 
    } else { 
     wakeSanta(); 
    } 
    main.semaphore.release(); 
} 

В случае, если вам интересно, когда использовать семафор с несколькими разрешениями, вот пример:

Предположим, у вас есть ресурс, который читается часто и изредка записывается. Поскольку чтение не изменяет значение (т. Е. Мутирует состояние), безопасно, чтобы несколько потоков считывали значение одновременно. Скажем, у нас есть n потоки и семафор с разрешениями n. Теперь поток должен получить одно разрешение для чтения - это означает, что все потоки могут считываться одновременно, но он должен получить все разрешения n, которые должны быть разрешены к записи, - в то время как один поток пишет, ни один другой поток не может получить доступ к ресурсу (ни читать, ни писать).


Лично я думаю о приобретении и отпускания семафора с одним разрешения как synchronized -блока. Однако обратите внимание на важное отличие, которое пользователь Ralf указал в комментарии: семафор может быть выпущен любым потоком, независимо от того, какой поток фактически получил разрешение. Семафоры более выразительны - с недостатком, что легче использовать их неправильно.

+1

Существует важное различие между семафором и мьютексом/синхронизированным блоком: блокировка, размещенная синхронизированным блоком, имеет нить-сродство, семафор не имеет. То есть в случае синхронизированного кода только поток, поместивший блокировку, может ее освободить. Но семафор может быть уменьшен одним потоком и увеличен другим. Таким образом, между ними существует весьма смысловая разница. – Ralf

+0

@ Ralf Большое спасибо за эту важную информацию. Размышление о сходствах семафоров и ключевом слова «synchronized» всегда помогало мне в понимании обоих, но мне никогда не приходило в голову, что можно реализовать семафор, выпущенный другим потоком, чем тот, который его приобретает. Для дальнейшего использования я сохранил аналогию с обновлением на основе вашего комментария, но переместил его на дно, поскольку он немного не по теме. –

1

Концептуально семафор поддерживает набор разрешений. Карта сайта хранит только количество доступных номеров и действует соответственно.

Вызов метода получения() будет блокироваться до тех пор, пока разрешения не станут доступными (больше нуля), а затем возьмут его (обратный отсчет). Каждый выпуск() добавляет разрешение.

Вы можете рассматривать второкурсника как охранника, который контролирует доступ людей к двери (логика, которой необходимо контролировать доступ к потоку). У него есть некоторые ключи (разрешения), чтобы войти в эту дверь().

Когда человек (нить) должен войти в эту дверь, ему нужно получить ключ (acquire()), или он ждет, пока не получит ключ (блоки резьбы). После того, как человек выходит из двери, он должен вернуть ключ (release()), чтобы другой человек мог получить.

Итак, в вашем существующем коде у вас есть 11 ключей для каждого потока, что не имеет смысла, так как у каждого человека теперь есть отдельный охранник, у которого есть 11 ключей.

Если вам нужно управлять только одной нитью makeToy(). Вы должны использовать семафор только с одним разрешением.

Semaphore semaphore = new Semaphore(1); 

И это должно быть публичным семафор, где нить акции, так что вы, вероятно, не экземпляр семафора внутри нити.

Вы можете попробовать это:

Main main = new Main(); 
    Semaphore semaphore = new Semaphore(1,true);//Only allow one thread to enter,so totally one permit is avaiable, and fairness =true 
    Elf elfOne = new Elf("Jim", main,semaphore);//pass the semaphore to all the threads 
    Elf elfTwo = new Elf("John", main,semaphore); 
    Elf elfThree = new Elf("Eamonn", main,semaphore); 
    Elf elfFour = new Elf("Eoin", main,semaphore); 
    ..... 


public class Elf extends Thread{ 

    Semaphore semaphore; 
    Random rng = new Random(); 
    String name; 
    Main main; 

    public Elf(String name, Main main,Semaphore semaphore){// need semaphore in constructors 
     this.name = name; 
     this.main = main; 
     this.semaphore=semaphore; 
    } 

Вот выход кода изменения:

... 
Filly created a toy! Toys remaining: 79 
Eamonn created a toy! Toys remaining: 78 
Eoin created a toy! Toys remaining: 77 
Jess created a toy! Toys remaining: 76 
Ronan created a toy! Toys remaining: 75 
Jim failed in making a toy! 
John created a toy! Toys remaining: 74 
Seamus created a toy! Toys remaining: 73 
Rebecca created a toy! Toys remaining: 72 
... 
Смежные вопросы