2016-03-08 3 views
1

Я пытаюсь реализовать условие, что доступ к одному потоку разрешен: допустим, это бутылка воды - я хочу, чтобы только 1 человек (нить) мог ее иметь время. Кажется, что все работает гладко, но я не могу получить печать, которая будет показана, - до вызова wait(); ,Java: wait() и notify() confusion

public synchronized void getBotttle { 
    while(myCondition) { 
     try { 
     System.out.println("Printing that is never done?!"); 
     wait(); 
     } 
     catch (InterruptedException e) {} 
    } 

    System.out.println("Printing that works"); 
    myCondition = true; 
    notifyAll(); //or notify(), tried both 

    try { 
     Thread.sleep(time); // 
    } 
    catch (InterruptedException e) {} 
    System.out.println("Printing again"); 
    methodToMakeConditionFalse(); 
    // notifyAll(); even if I put it here its still the same 
} 

Этот метод вызывается потоками, и она работает как задумано - только одна нить имеет «бутылка», но печать не существует. Любые идеи?

+1

это трудно сказать, не будучи в состоянии см. окружающий код, но похоже, что значение 'myCondition' должно быть' false', что мешает войти в ваш цикл while; в противном случае 'System.out.println' должно быть выполнено. Еще одна вещь, которую я вижу: ваш код отформатирован так, как будто вы включаете полную структуру 'while'; однако скобки не совпадают по всему образцу кода, поэтому закрывающая скобка ('}'), которая следует за вашей первой 'try'-'catch', фактически закрывает цикл while. Правильно ли ваша структура кода? –

+0

Просто, чтобы быть ясным, это все действительно только один блок кода? Или это два блока кода, каждый из которых работает в одном потоке? Как и сейчас, если предположить, что все это окружено блоком «synchronized» (который вы не включили в вопрос), метод 'wait()' будет ждать бесконечно для 'notify()' _ из другого потока_, который будет никогда не приходите. Уведомление не может исходить из этого потока, потому что поток никогда не дойдет до него, потому что он все еще ждет уведомления. Вы, по сути, зашли в тупик. – yshavit

+0

Отредактировано мое сообщение, чтобы его было легче читать и понимать. Это 1 блок кода - синхронизированный метод, который Threads вызывает, когда им приходится использовать «общий» объект. – LFCLPFC

ответ

0

Вы не полный пример, который делает его трудно сказать, что вы делаете неправильно; я предполагаю, что ваш флаг состояния не установлен соответствующим образом. Вот полный пример, который работает, он гарантирует, что только один поток имеет доступ к ресурсу за раз.

public class StuffExample { 

    public static void main(String[] args) throws Exception { 

     Worker worker = new Worker(new StuffHolder()); 
     Thread t1 = new Thread(worker); 
     Thread t2 = new Thread(worker); 

     t1.start(); 
     t2.start(); 

     Thread.sleep(10000L); 
     t1.interrupt(); 
     t2.interrupt(); 
    } 
} 

class Worker implements Runnable { 
    private StuffHolder holder; 

    public Worker(StuffHolder holder) { 
     this.holder = holder; 
    } 

    public void run() { 
     try { 
      while (!Thread.currentThread().isInterrupted()) { 
       holder.useStuff(); 
       Thread.sleep(1000L); 
      } 
     } 
     catch (InterruptedException e) { 
     } 
    } 
} 

class StuffHolder { 

    private boolean inUse = false; 
    private int count = 0; 
    public synchronized void useStuff() throws InterruptedException { 
     while (inUse) { 
      wait(); 
     } 
     inUse = true; 
     System.out.println("doing whatever with stuff now, count=" 
      + count + ", thread=" + Thread.currentThread().getName()); 
     count += 1; 
     inUse = false; 
     notifyAll(); 
    } 
} 

Выход:

doing whatever with stuff now, count=0, threadid=Thread-0 
doing whatever with stuff now, count=1, threadid=Thread-1 
doing whatever with stuff now, count=2, threadid=Thread-0 
doing whatever with stuff now, count=3, threadid=Thread-1 
doing whatever with stuff now, count=4, threadid=Thread-0 
doing whatever with stuff now, count=5, threadid=Thread-1 
doing whatever with stuff now, count=6, threadid=Thread-0 
doing whatever with stuff now, count=7, threadid=Thread-1 
doing whatever with stuff now, count=8, threadid=Thread-0 
doing whatever with stuff now, count=9, threadid=Thread-1 
doing whatever with stuff now, count=10, threadid=Thread-0 
doing whatever with stuff now, count=11, threadid=Thread-1 
doing whatever with stuff now, count=12, threadid=Thread-0 
doing whatever with stuff now, count=13, threadid=Thread-1 
doing whatever with stuff now, count=14, threadid=Thread-0 
doing whatever with stuff now, count=15, threadid=Thread-1 
doing whatever with stuff now, count=16, threadid=Thread-1 
doing whatever with stuff now, count=17, threadid=Thread-0 
doing whatever with stuff now, count=18, threadid=Thread-1 
doing whatever with stuff now, count=19, threadid=Thread-0 

См Oracle's tutorial on guarded blocks.

+0

Какая нить может быть достигнута во время (inUse) состояния? Подозревать никто, кроме самого первого. Таким образом, это просто подход к переустановке мьютекса, который уже реализован на Java и называется монитором. – Ivan

+0

@Ivan: добавлено имя потока, чтобы показать, что оба потока обращаются к этому –

+0

Я могу отметить только один ответ в качестве лучшего ответа, и я отметю это, но и вы, и Иван многое помогли, спасибо большое. – LFCLPFC

0

Фактически ответ очень прост, подпись вашего метода getBotttle() имеет ключевое слово synchronized, что означает, что никогда и два разных потока не будут обращаться к этому коду одновременно. Таким образом, цельный блок с while(myCondition) { ... } бесплоден.

Во-вторых, я рекомендую ознакомиться с пакетом java.util.concurrent.*.

UPD. Кажется, стоит уточнить, что обычно UseCase для ожидания/notifyAll является:

public class WaitNotify { 

    public static void main(String[] args) throws InterruptedException { 
     new WaitNotify().go(); 
    } 

    private void go() throws InterruptedException { 
     ResourceProvider provider = new ResourceProvider(); 
     Consumer c1 = new Consumer("consumer1", provider); 
     Consumer c2 = new Consumer("consumer2", provider); 
     Consumer c3 = new Consumer("consumer3", provider); 
     Consumer[] consumers = new Consumer[] { c1, c2, c3 }; 

     for (int i = 0; i < consumers.length; i++) { 
      provider.grant(consumers[i]); 
     } 
    } 

    public static class ResourceProvider { 
     private Resource resource = new Resource(); 

     public synchronized void grant(Consumer consumer) throws InterruptedException { 
      while (resource == null) { 
       wait(); 
      } 
      consumer.useResource(resource); 
      resource = null; 
     } 

     public synchronized void putBack(Resource resource) { 
      this.resource = resource; 
      notifyAll(); 
     } 
    } 

    public static class Resource { 
     public void doSomething(String consumer) { 
      System.out.println("I'm working! " + consumer); 
      try { 
       Thread.sleep(3L * 1000L); 
      } catch (InterruptedException e) { } 
     } 
    } 

    public static class Consumer implements Runnable { 
     private String consumer; 
     private Resource resource; 
     private ResourceProvider provider; 

     public Consumer(String consumer, ResourceProvider provider) { 
      this.consumer = consumer; 
      this.provider = provider; 
     } 

     public void useResource(Resource r) { 
      this.resource = r; 
      new Thread(this).start(); 
     } 

     @Override 
     public void run() { 
      resource.doSomething(consumer); 
      provider.putBack(resource); 
     } 
    } 
} 
0

Большое спасибо вам обоим. Я попробую написать все ясно, чтобы другие люди застряли в чем-то подобном, могут это исправить.

У меня было 2 нитей (скажем, 2 человека). Они оба должны пить воду из 1 бутылки, поэтому, когда бутылка используется, 2-й человек должен ждать. Мой код выглядел примерно так:

class Bottle{ 
private boolean inUse=false; 

public synchronized void getBotttle(String name, int time) { 
    while(inUse) { 
     try { 
     System.out.println("The bottle is in use. You have to wait"); 
     wait(); 
     } 
     catch (InterruptedException e) {} 
    } 

    System.out.println("Person "+name+" is using the bottle"); 
    inUse = true; 
    notify(); //or notifyAll(), tried both 

    try { 
     Thread.sleep(time); // sleep the Thread with the person that is drinking at the moment for some time in order for him to finish 
    } 
    catch (InterruptedException e) {} 
    System.out.println("The bottle is now free"); 
    inUse=false; 
    // notify(); even if I put it here its still the same 
} 
} 

Я только начал использовать темы в Java, так что я не был уверен, где уведомит() действительно должны идти. Более того, я не понял, что notify() освобождает блокировку только после того, как выполняется весь блок с синхронизированным ключевым словом. В моем случае это было не то, что я хотел, и как это происходит при освобождении блокировки, условие для метода while будет ложным, и печать не будет выполнена. Тот факт, что программа ждала должным образом и по назначению затрудняла мне это.

И это то, что я хотел, и то, что я начала работать:

class Bottle{ 
private boolean inUse=false; 

public void getBotttle(String name, int time) { 
    while(inUse) { 
     try { 
     System.out.println("The bottle is in use. You have to wait."); 
     synchronized(this){ 
     wait(); 
     } 
     } 
     catch (InterruptedException e) {} 
    } 

    System.out.println("Person "+name+" is using the bottle"); 
    inUse = true; 

    try { 
     Thread.sleep(time); // sleep the Thread with the person that is drinking at the moment for some time in order for him to finish 
    } 
    catch (InterruptedException e) {} 
    System.out.println("The bottle is free now."); 
    inUse=false; 
    synchronized(this){ 
    notifyAll(); 
    } 
} 
} 

Надеюсь, последнее редактирование: Это должно предотвратить 2 потока от пропуска время цикла и должно быть решение, которое я искал

class Bottle{ 
private boolean inUse=false; 

public synchronized void getBotttle(String name, int time) { 
    while(inUse) { 
     try { 
     System.out.println("The bottle is in use. You have to wait."); 
     wait(); 
     } 
     catch (InterruptedException e) {} 
    } 

    System.out.println("Person "+name+" is using the bottle"); 
    inUse = true; 
} 

public synchronized void sleeping(String name, int time) 
    try { 
     Thread.sleep(time); // sleep the Thread with the person that is drinking at the moment for some time in order for him to finish 
    } 
    catch (InterruptedException e) {} 
    notifyAll(); 
    System.out.println("The bottle is free now."); 
    inUse=false; 
} 
} 

Edit: Guess нет, печать, что бутылка используется не выполняется agian ...

+0

из последней части кода, похоже, вы пропустили точку. если вы отпустите блокировку, а затем установите флаг, вы разрешаете другому потоку входить и изменять флаг и даже получать доступ к общему ресурсу, так что это совсем не потокобезопасность. взгляните на учебники Oracle, с которыми я связан. –

+0

Не то, что в учебнике аналогично тому, что я делаю, только мои фактические потоки, которые используют объект, находятся в другом классе, и в какой-то момент они называют Bottle.getBottle(); первый, чтобы вызвать его, пропустит это время, потому что изначально условие ложно, будет печатать, что у него есть бутылка, устанавливает флаг в true и спит в течение некоторого времени. К тому времени, когда он спит, поток 2 пытается вызвать Bottle.getBottle(); и попадает в цикл while и ждет там до тех пор, пока Thread 1 не просыпается и изменяет флаг на false и уведомляет другой поток о том, что флаг был изменен? – LFCLPFC

+0

После того, как поток перестает ждать и отказывается от блокировки и * до *, этот поток может установить isUsed в true, другой поток может ввести тот же метод, пропустить мимо цикла и установить isUsed в true. как только вы отпустите блокировку, вы не можете знать, что такое состояние системы. –

Смежные вопросы