Sterowniki urządzeń są typowym przykładem systemów, które są implementowane jako programy współbieżne. Należą do nich, na przykład: systemy dostępu, automatyczny parking, inteligentny dom, sprzęt AGD i RTV, systemy alarmowe, elektronika samochodu, urządzenia medyczne.
Zazwyczaj takie systemy implementuje się w postaci kilku(nastu) komunikujących się wątków (zadań) działających nieprzerwanie. Są one odpowiedzialne za reakcje na zdarzenia generowane przez urządzenia wejściowe (przyciski i czujniki) oraz realizację sterowania i generację akcji wyjściowych.
Często stosowane są dwa wzorce komunikacji.
Zrealizujemy przykład sterownika windy. Obiekty składowe pokazane są na rysunku.
boolean[]
. Jedna przechowuje informacje o wezwaniach przy ruchu w góre, druga przy ruchu w dół. Jest to dzielony zasób, dostęp do niego powinien być zabezpieczony.ElevatorStops
i podejmuje decyzje o ruchu lub zatrzymaniuExternalPanelsAgent
(lub odpowiednio InternalPanelAgent
).ExternalPanelsAgent
oczekuje na komunikat w kolejce. Po jego pojawieniu się zapisuje wezwanie do jednej z tablic w ElevatorStops
. InternalPanelAgent
działa analogicznieElevatorCar
Kod jest prawie gotowy…
Do uzupełnienia Dodaj elementy, które zagwarantują, że klasa będzie singletonem
get()
ma zwracać jedyną instancję klasypublic class ElevatorStops { static final int FLOORS = 10; static final int MIN_FLOOR = 0; static final int MAX_FLOOR=FLOORS-1; boolean stopsDown[] = new boolean[FLOORS]; boolean stopsUp[] = new boolean[FLOORS]; void setLiftStopUp(int floor){ stopsUp[floor]=true; } void setLiftStopDown(int floor){ stopsDown[floor]=true; } void clearStopUp(int floor){ stopsUp[floor]=false; } void clearStopDown(int floor){ stopsDown[floor]=false; } boolean hasStopAbove(int floor){ for(int i=floor+1;i<MAX_FLOOR;i++){ if(stopsUp[i] || stopsDown[i])return true; } return false; } boolean hasStopBelow(int floor){ for(int i=floor-1;i>=MIN_FLOOR;i--){ if(stopsUp[i] || stopsDown[i])return true; } return false; } int getMaxSetFloor(){ for(int i=MAX_FLOOR-1;i>=0;i--){ if(stopsUp[i]||stopsDown[i])return i; } return 0; } int getMinSetFloor(){ for(int i=0;i<MAX_FLOOR;i++){ if(stopsUp[i]||stopsDown[i])return i; } return 0; } boolean whileMovingDownSholudStopAt(int floor){ return stopsDown[floor]; } boolean whileMovingUpSholudStopAt(int floor){ return stopsUp[floor]; } static ElevatorStops get(){ return ??? } }
ElevatorStops
jest dzielonym zasobem. Przy dostępie współbieżnym potencjalnie istnieje ryzyko wprowadzenia zasobu w stan niespójności. Tutaj ryzyko jest niewielkie, ponieważ są to tablice boolean[]
, w których zmieniane
lub odczytywane są pojedyncze wartości. Gdyby jednak zasób miał zostać zabezpieczony, stosowanym w Javie rozwiązaniem jest zadeklarowanie metod dostępu jako synchroniczne (słowo kluczowe synchronized
). Jakie byłyby konsekwencje zmiany sposobu implementacji?
W obiekcie klasy jest przechowywana informacja o numerze pietra (atrybut floor
).
Stan obiektu jest kombinacją dwóch zbiorów stanów:
enum Tour {UP, DOWN}
enum Movement {STOP,MOVING};
public class ElevatorCar extends Thread{ int floor=0; public int getFloor() { return floor; } enum Tour {UP, DOWN}; Tour tour = Tour.UP; enum Movement {STOP,MOVING}; Movement movementState = Movement.STOP; }
Sterowanie jest zrealizowane wewnątrz metody run()
.
Sterownik windy powinien
floor
, tour
i movementState
ElevatorStops
Zasady:
public void run(){ for(;;){ //sleep(500); } }
Klasa definiuje zagnieżdżoną klasę komunikatów ExternalCall
, które mają trafiać do kolejki wejściowej. Ponieważ potrzebuje informacji o pietrze - odczytuje wartość floor
z ElevatorCar
.
public class ExternalPanelsAgent extends Thread{ private final ElevatorCar elevatorCar; static class ExternalCall{ private final int atFloor; private final boolean directionUp; ExternalCall(int atFloor,boolean directionUp){ this.atFloor = atFloor; this.directionUp = directionUp; } } BlockingQueue<ExternalCall> input = new ArrayBlockingQueue<ExternalCall>(100); ExternalPanelsAgent(ElevatorCar elevatorCar){ this.elevatorCar = elevatorCar; } public void run(){ for(;;){ ExternalCall ec = ec = input.take(); // ignorujemy wezwanie na piętro, na którym winda się znajduje if(ec.atFloor==elevatorCar.getFloor())continue; // dodajemy do jednej z tablic zgłoszeń if(ec.directionUp){ ElevatorStops.get().setLiftStopUp(ec.atFloor); }else{ ElevatorStops.get().setLiftStopDown(ec.atFloor); } } } }
Napisz ją w podobny sposób
public class InternalPanelAgent extends Thread { static class InternalCall{ private final int toFloor; InternalCall(int toFloor){ this.toFloor = toFloor; } } InternalPanelAgent(ElevatorCar elevatorCar){ this.elevatorCar = elevatorCar; } BlockingQueue<InternalCall> input = new ArrayBlockingQueue<>(100); ElevatorCar elevatorCar; public void run(){ for(;;){ // odczytaj wezwanie z kolejki // w zależności od aktualnego piętra, na którym jest winda, // umieść przystanek w odpowiedniej tablicy ''EleveatorStops'' } } }
Klasa Elevator
łączy wszystkie elementy
public class Elevator { // tworzymy 3 wątki static ElevatorCar car = new ElevatorCar(); static ExternalPanelsAgent externalPanelAgent = new ExternalPanelsAgent(car); static InternalPanelAgent internalPanelAgent = new InternalPanelAgent(car); // symulacja przywołania windy z panelu zewnętrznego static void makeExternalCall(int atFloor,boolean directionUp){ try { externalPanelAgent.input.put(new ExternalPanelsAgent.ExternalCall(atFloor,directionUp)); } catch (InterruptedException e) { e.printStackTrace(); } } // symulacja wyboru pietra w panelu wewnętrznym static void makeInternalCall(int toFloor){ try { internalPanelAgent.input.put(new InternalPanelAgent.InternalCall(toFloor)); } catch (InterruptedException e) { e.printStackTrace(); } } // uruchomienie wątków static void init(){ car.start(); externalPanelAgent.start(); internalPanelAgent.start(); } // miesjce na kod testowy public static void main(String[] args) { init(); makeExternalCall(4,false); Thread.currentThread().sleep(100); makeInternalCall(2); } }
Przetestuj różne sekwencje wywołań i sprawdź czy wina zachowuje się poprawnie. Napisz co najmniej 5 różnych scenariuszy (sekwencji wywołań). W przypadku błędnego działania - popraw kod w ElevatorCar
.
Winda powinna: