2015-03-25 6 views
0


У меня проблема с пониманием Locks and Conditions на Java, я не понимаю, почему мой код попадает в тупик.
Моя программа состоит из Mainthread и Subthread, subthread является членом Mainthread. Оба потока выполняются в бесконечном цикле, цикл Subthread должен выполнять ровно одну итерацию, как только он получает сигнал для startCond из Mainthread. Mainthread должен дождаться окончания сигнала finishCond.Синхронизация нитей с замками

import java.util.concurrent.locks.Condition; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

public class LockTest { 

    public static void main(String[] args) { 
     LockTest lt = new LockTest(); 
     Mainthread m1 = lt.new Mainthread(); 
     m1.start(); 
    } 

    public class Mainthread extends Thread { 
     private Subthread sub = new Subthread(); 

     public void run(){ 
      System.out.println("Main start"); 
      sub.start(); 

      while(!isInterrupted()) { 
       try { 
        sub.getStartLock().lock(); 
        sub.getStartCond().signal(); 
        sub.getStartLock().unlock(); 

        sub.getFinishLock().lock(); 
        sub.getFinishCond().await(); 
        sub.getFinishLock().unlock(); 
        System.out.println("Main done"); 
       } catch(InterruptedException e) { 
        e.printStackTrace(); 
       } 

      } 
     } 
    } 

    public class Subthread extends Thread { 
     private Lock startLock = new ReentrantLock(); 
     private Lock finishLock = new ReentrantLock(); 
     private Condition startCond = startLock.newCondition(); 
     private Condition finishCond = finishLock.newCondition(); 


     public Lock getStartLock() { 
      return startLock; 
     } 

     public Lock getFinishLock() { 
      return finishLock; 
     } 

     public Condition getStartCond() { 
      return startCond; 
     } 

     public Condition getFinishCond() { 
      return finishCond; 
     } 

     public void run() { 
      System.out.println("Sub start"); 
      while(!isInterrupted()) { 
       try { 
        startLock.lock(); 
        startCond.await(); 
        startLock.unlock(); 

        finishLock.lock(); 
        finishCond.signal(); 
        finishLock.unlock(); 

        System.out.println("Sub done"); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 
} 

Мой ожидаемый результат будет:
Main сделано
Sub сделано
(повторяется столько раз, сколько это было выполнено в петлях).

Есть ли способ решить эту проблему проще?

ответ

0

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

Главное, вызывает sub.getStartCond(). Signal(); но в этот момент вспомогательный поток все еще не работает, поэтому он пропускает этот сигнал.

Главное, ждет на отделке.

Sub запускает свой метод запуска, он переходит в состояние запуска и ждет его навсегда.

Тупик.

Сигнал просыпается только ТОЛЬКО ожидающий поток, он не «запоминает» предыдущие вызовы.

Использование семафор вместо http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html

имеет семантику 'подсчета разрешений.

+0

Спасибо, просто Thread.yield() решил это. Я сделаю это принятым ответом. – derdoe

+1

выход НЕ РЕШАЕТ, он только увеличивает шансы, что он будет работать. Доходность - это лишь намек на планировщик потоков. Его можно игнорировать. Более того, другой поток может начинаться до подпотока, а затем Main может продолжаться до того, как будет запущен sub. – Zielu

+0

также я не знаю, где вы вкладываете свой доход, но любая итерация вашего цикла воссоздает проблему. После ожидания сигнал может быть отправлен до того, как другой поток начнет его ожидание. – Zielu

0

Возможно, существует более надежный способ сделать это. Я бы рекомендовал использовать CountDownLatch, инициализированный с числом 1, а не условием. И основной, и дочерний потоки могут использовать один и тот же экземпляр защелки (поскольку основной владелец ребенка должен быть легким). Ребенок назовет await(), а главное вызовет countDown(), когда вам нужно отправить сигнал ребенку. Я рекомендую вам сделать защелку private и final.

class ChildThread extends Thread { 
    private final CountDownLatch signal; 

    public ChildThread(CountDownLatch signal) { 
    this.signal = signal; 
    } 

    public void run() { 
    // The loop is necessary in case we get interrupted. 
    while (true) { 
     try { 
     signal.await(); 
     break; 
     } catch(InterruptedException ignored) { 
     } 
    } 
    // do the work... 
    } 
} 

class MainThread extends Thread { 
    private final ChildThread child; 
    private final CountDownLatch signalToChild; 
    public MainThread() { 
    signalToChild = new CountDownLatch(1); 
    child = new ChildThread(signalToChild); 
    } 

    public void run() { 
    // I can start the child right away but I'd rather make sure it 
    // starts if the main thread has started. 
    child.start(); 
    // prework 
    // let's signal the child 
    signalToChild.countDown(); 
    // now the child is working, let's go on with the main thread work 
    } 
} 

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

+0

Спасибо, посмотрите на свое предложение с помощью CountDownLatch! :) – derdoe

+0

Прошу!Объекты параллелизма более высокого уровня гораздо легче рассуждать и, как правило, более явные и, следовательно, надежные, т. Е. Сложнее совершать ошибки. Также я рекомендую проверить превосходный «Java Concurrency на практике» Брайана Гетца, если вы еще этого не сделали. –

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

  • Нет связанных вопросов^_^