2015-06-04 3 views
0

У меня есть два синхронизированных метода, и я использую шаблон проектирования медиатора. Я пытаюсь избежать тупиков, которые (из того, что я понимаю), например, когда поток имеет блокировку переменной res1, но для блокировки переменной res2 требуется блокировка. Другой поток нуждается в блокировке для res1, но имеет блокировку для res2 - что приводит к тупиковой ситуации, верно?Синхронизированные методы, чтобы избежать тупика

Предполагая, что мое понимание взаимоблокировок верное, тогда мой вопрос заключается в том, решила ли я проблему тупика в этом коде?

У меня есть два синхронизированных метода и два потока.

public class Producer extends Thread { 
    private Mediator med; 
    private int id; 
    private static int count = 1; 

    public Producer(Mediator m) { 
     med = m; 
     id = count++; 
    } 

    public void run() { 
     int num; 
     while(true) { 
      num = (int)(Math.random()*100); 
      med.storeMessage(num); 
      System.out.println("P-" + id + ": " + num); 
     } 
    } 
} 

public class Consumer extends Thread { 
    private Mediator med; 
    private int id; 
    private static int count = 1; 

    // laver kopling over til mediator 
    public Consumer(Mediator m) { 
     med = m; 
     id = count++; 
    } 

    public void run() { 
     int num; 
     while(true) { 
      num = med.retrieveMessage(); 
      System.out.println("C" + id + ": " + num); 
     } 
    } 
} 

public class Mediator { 
    private int number; 
    private boolean slotFull = false; 

    public synchronized void storeMessage(int num) { 
     while(slotFull == true) { 
      try { 
       wait(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
     slotFull = true; 
     number = num; 
     notifyAll(); 
    } 

    public synchronized int retrieveMessage() { 
     while(slotFull == false) { 
      try { 
       wait(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
     slotFull = false; 
     notifyAll(); 
     return number; 
    } 
} 

public class MediatorTest { 
    public static void main(String[] args) { 
     Mediator mb = new Mediator(); 
     new Producer(mb).start(); 
     new Producer(mb).start(); 
     new Producer(mb).start(); 

     new Consumer(mb).start(); 
     new Consumer(mb).start(); 
    } 
} 

ответ

3

, например, когда поток имеет блокировку на переменную res1, но нуждается в замок на переменной res2

важно не то, что есть два переменные, важно то, что должно быть два (или более) замки.

Имена «res1» и «res2» предназначены для того, чтобы предлагать ресурсы , каждый из которых может иметь одну или несколько переменных, каждая из которых имеет свой собственный замок. Вот где вы попали в беду:

final Object lock1 = new Object(); 
final Object lock2 = new Object(); 

public void method1() { 
    synchronized (lock1) { 
     // Call Thread.sleep(1000) here to simulate the thread losing its time slice. 
     synchronized(lock2) { 
      doSomethingThatRequiresBothLocks 
     } 
    } 
} 

public void method2() { 
    synchronized (lock2) { 
     // Do the same here 'cause you can't know which thread will get to run first. 
     synchronized(lock1) { 
      doSomethingElseThatRequiresBothLocks() 
     } 
    } 
} 

Если поток А вызывает method1(), есть очень маленький шанс, что он может потерять свой квант времени (т.е. повернет запустить) только после того, как он успешно блокирует lock1, но перед этим замки lock2.

Затем, пока нить A ждет своей очереди, чтобы запустить снова, поток B вызывает method2(). Thread B сможет заблокировать lock2, но затем он застревает, потому что lock1 заблокирован нитью A. Кроме того, когда поток A снова запускается, он будет немедленно заблокирован, когда он попытается заблокировать lock2, который принадлежит теме B. Ни одна нить никогда не сможет продолжить с этого момента.


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

1

Ваше понимание основной проблемы тупиковой правильно. С вашим вторым вопросом о правильности вашего решения проблемы взаимоблокировки у вас есть только 1 блокировка, поэтому я бы сказал «да» по умолчанию, так как описанный вами тупик невозможен в этой ситуации

+0

Спасибо :) я получил экзамен через несколько дней, и я планирую на описании того, как решать взаимоблокировки. Это требует хорошего дизайна, но я подумал, что это будет выглядеть как вышеупомянутый код. Вы знаете, как решать взаимоблокировки? Я думаю, может быть, теоретическое объяснение? – Charles

0

Я согласен с тем, что сказал @ControlAltDel. И ваше понимание тупика совпадает с моим. В то время как существует несколько различных способов, которыми может проявляться тупик, то, как вы описываете, - непоследовательное приобретение нескольких мониторов вовлеченными потоками (методами) вызывает тупик.

Другой способ заключается в том, чтобы (например,) спать, удерживая замок. Поскольку вы правильно закодировали, когда производитель обнаружил, что slotFull = true, он ждет, , отказываясь от блокировки, так что другая нить (потребитель, которая использует один и тот же экземпляр Mediator с производителем) может сделать прогресс потенциально заставляя эту нить также сделать прогресс после получения уведомления. Если вы решили вместо этого позвонить Thread.sleep() (наивно надеясь, что кто-то вызовет спать, когда условие будет ложным), тогда это вызовет тупик, потому что этот поток спал, все еще удерживая блокировку, лишая доступ к другому потоку ,

-1

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

Подойдя к вашей проблеме, он не будет заторможен.

Если у вас есть два независимых атрибута в классе, разделяемом несколькими потоками, вы должны синхронизировать доступ к каждой переменной, но нет проблем, если один поток обращается к одному из атрибутов, а другой поток обращается к другому при одном и том же время.

class Cinema { 
private long vacanciesCinema1; private long vacanciesCinema2; 
private final Object controlCinema1, controlCinema2; 
public Cinema() { 
    controlCinema1 = new Object(); 
    controlCinema2 = new Object(); 
    vacanciesCinema1 = 20; 
    vacanciesCinema2 = 20; 
} 
public boolean sellTickets1(int number) { 
    synchronized (controlCinema1) { 
     if (number < vacanciesCinema1) { 
      vacanciesCinema1 -= number; 
      return true; 
     } else { 
      return false; 
     } 
    } 
} 
public boolean sellTickets2(int number) { 
    synchronized (controlCinema2) { 
     if (number < vacanciesCinema2) { 
      vacanciesCinema2 -= number; 
      return true; 
     } else { 
      return false; 
     } 
    } 
} 
public boolean returnTickets1(int number) { 
    synchronized (controlCinema1) { 
     vacanciesCinema1 += number; 
     return true; 
    } 
} 
public boolean returnTickets2(int number) { 
    synchronized (controlCinema2) { 
     vacanciesCinema2 += number; 
     return true; 
    } 
} 
public long getVacanciesCinema1() { 
    return vacanciesCinema1; 
} 

public long getVacanciesCinema2() { 
    return vacanciesCinema2; 
} 

}

class TicketOffice1 implements Runnable { 
private final Cinema cinema; 

public TicketOffice1(Cinema cinema) { 
    this.cinema = cinema; 
} 
@Override 
public void run() { 
    cinema.sellTickets1(3); 
    cinema.sellTickets1(2); 
    cinema.sellTickets2(2); 
    cinema.returnTickets1(3); 
    cinema.sellTickets1(5); 
    cinema.sellTickets2(2); 
    cinema.sellTickets2(2); 
    cinema.sellTickets2(2); 
} 

}

public class CinemaMain { 
public static void main(String[] args) { 
    Cinema cinema = new Cinema(); 
    TicketOffice1 ticketOffice1 = new TicketOffice1(cinema); 
    Thread thread1 = new Thread(ticketOffice1, "TicketOffice1"); 
    TicketOffice2 ticketOffice2 = new TicketOffice2(cinema); 
    Thread thread2 = new Thread(ticketOffice2, "TicketOffice2"); 
    thread1.start(); 
    thread2.start(); 
    try { 
     thread1.join(); 
     thread2.join(); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
    System.out.printf("Room 1 Vacancies: %d\n", cinema.getVacanciesCinema1()); 
    System.out.printf("Room 2 Vacancies: %d\n", cinema.getVacanciesCinema2()); 
} 

}

+0

Синхронизация не предотвращает блокировку. Напротив, синхронизация без принятия надлежащих мер предосторожности может привести к возникновению взаимоблокировки. – JimN

+0

Да, синхронизировать ключевое слово следует использовать с осторожностью. – deepak