воскресенье, 3 февраля 2013 г.

Взаимодействие между потоками JAVA или задачка 'Робот'

В процессе профессионально деятельности встретилась задачка. К своему стыду решить её на вскидку не смог :( Так что решил заполнить пробелы в знаниях статьей....


Задачка


Система управления ногами робота состоит из двух потоков. Каждый поток отвечает за шаг одной ногой(правой и левой). Необходимо написать алгоритм синхронизации двух потоков так чтобы робот мог ходить, т.е делать шаги правой а затем левой ногой.
  public class Main {
    public static void main(String[] args) {
        new Thread(new Leg("left")).start();
        new Thread(new Leg("right")).start();        
        Thread.sleep(2000);
    }
  }

  class Leg implements Runnable{
    
    private String name;
    
    public Leg(String name){
        this.name = name;
    }
    @Override
    public void run() {
        while (true) {
            System.out.println(name);
        }
    }
  }

Решение I (wait/notify)


Для решения задачи будем использовать встроенные средства синхронизации потоков языка java - монитор и блоки synchronized. А также методы взаимодействия потоков (wait(), notify()).
Основное назначение перечисленных выше средств следующее:

  •  монитор - объект осуществляющий контроль доступа потоков к исполнению кода в критических секциях.

  • блок synchronized - определяет границы критических секций монитора  т.е участков кода в которых может исполнятся одновременно только один поток .
  • метод wait - используется в критических секциях для остановки исполнения потока с освобождением блокировки монитора (т.е другой поток ожидающий входа в критическую секцию монитора после выполнения метода wait сможет начать исполнение критической секции монитора)
  • метод notify - используется в критических секциях для восстановления работы одного из потоков остановленных в данном мониторе.
  public class WaitNotifyMain {
    public static void main(String[] args) {
        
        Object monitor = new Object();
        
        new Thread(new Leg("left",monitor)).start();
        new Thread(new Leg("right",monitor)).start();
        
        Thread.sleep(2000);
    }
}

class Leg implements Runnable{
    
    private String name;
    
    private Object monitor;
    
    public Leg(String name,Object monitor){
        this.name = name;
        this.monitor = monitor;
    }

    @Override
    public void run() {
        try{
        while (true) {
            synchronized (monitor) {
                monitor.notify();
                monitor.wait();
                System.out.println(name);
            }
        }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Решение II (Condition)


Решение основано на классе ReentrantLock который представляет собой “Усовершенствование synchronized java”, появившейся в jdk1.5

public class CoditionMain {
     
    public static void main(String[] args) {
        
        final Lock lock = new ReentrantLock();
        final Condition monitor  = lock.newCondition(); 
        System.out.println("Hello world!!");
        
        new Thread(new LegCodition("left",
                                   monitor,lock)).start();
        new Thread(new LegCodition("right",
                                   monitor,lock)).start();
        
        Thread.sleep(2000);
    }
    

}
class LegCodition implements Runnable{
    
    private String name;
    private Condition monitor;
    private Lock lock;
    
    public LegCodition(String name,
                       Condition monitor,Lock lock){
        this.name = name;
        this.lock=lock;
        this.monitor = monitor;
    }
    
    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
                monitor.signal();
                monitor.await();
                System.out.println(name);
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
         }
    }
}

Решение III (Queue)


Это все лишь нестандартное применение шаблона producer-consumer на основе LinkedBlockingQueue

public class QueueMain {

   public static void main(String[] args){
           
      LinkedBlockingQueue 
         queueToLeft = new LinkedBlockingQueue();
      LinkedBlockingQueue 
         queueToRigth = new LinkedBlockingQueue();
            
      queueToRigth.put(new Object());
            
       new Thread(new LeftLeg("left",
                              queueToLeft,
                              queueToRigth)).start();
       new Thread(new RightLeg("right",
                              queueToLeft,
                              queueToRigth)).start();
            
            Thread.sleep(2000);
   } 
}

class LeftLeg implements Runnable{
    
    private String name;
    
    private LinkedBlockingQueue queueToLeft;
    private LinkedBlockingQueue queueToRigth;
    
    public LeftLeg(String name,
                   LinkedBlockingQueue queueToLeft,
                   LinkedBlockingQueue queueToRigth){
        this.name = name;
        this.queueToLeft = queueToLeft;
        this.queueToRigth=queueToRigth;
    }

    @Override
    public void run() {
        try{
        while (true) {
            synchronized (this) {
                Object obj=queueToRigth.take();
                System.out.println(name);
                queueToLeft.put(queueToLeft);
            }
        }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

class RightLeg implements Runnable{
    
    private String name;
       
    private LinkedBlockingQueue queueToLeft;
    private LinkedBlockingQueue queueToRigth;
    
    public RightLeg(String name,
                   LinkedBlockingQueue queueToLeft,
                   LinkedBlockingQueue queueToRigth){
        this.name = name;
        this.queueToLeft = queueToLeft;
        this.queueToRigth=queueToRigth;
    }

       @Override
       public void run() {
           try{
           while (true) {
               synchronized (this) {
                   Object obj= queueToLeft.take();
                   System.out.println(name);
                   queueToRigth.put(queueToLeft);
               }
           }
           } catch (Exception e) {
               e.printStackTrace();
           }
       }       
   }

Ресурсы

1. Статья "Синхронизация потоков"
2. Брюс Эккель. Философия Java = Thinking in Java. — 3-е изд. — СПб.: Питер, 2003. — 976 с. — ISBN 5-88782-105-1
3. Статья Использование языка Java для разработки параллельных приложений

Комментариев нет:

Отправить комментарий