#java #synchronization
#java #синхронизация
Вопрос:
Я хочу иметь возможность изменить метод runTrain(), чтобы обеспечить синхронизацию, чтобы избежать одновременного прохождения двух поездов.
общедоступный класс Peru расширяет Railway {
public Peru() throws SetUpError {
super("Peru",new Delay(0.1,0.3));
}
/**
* Run the train on the railway.
* This method currently does not provide any synchronisation to avoid two
* trains being in the pass at the same time.
*/
public void runTrain() throws RailwaySystemError {
Clock clock = getRailwaySystem().getClock();
Railway nextRailway = getRailwaySystem().getNextRailway(this);
while (!clock.timeOut()) {
choochoo();
getBasket().putStone(this);
while (nextRailway.getBasket().hasStone(this)) {
getBasket().takeStone(this);
siesta();
getBasket().putStone(this);
}
crossPass();
getBasket().takeStone(this);
}
}
}
Проблема на данный момент заключается в том, что мой текущий метод не позволяет синхронизировать одновременное прохождение двух поездов. Два поезда — Перу и Боливия.
общественный класс Боливия расширяет Железную дорогу {
public Bolivia() throws SetUpError {
super("Bolivia",new Delay(0.1,0.3));
}
/**
* Run the train on the railway.
* This method currently does not provide any synchronisation to avoid two
* trains being in the pass at the same time.
*/
public void runTrain() throws RailwaySystemError {
Clock clock = getRailwaySystem().getClock();
Railway nextRailway = getRailwaySystem().getNextRailway(this);
while (!clock.timeOut()) {
choochoo();
getBasket().putStone(this);
while (nextRailway.getBasket().hasStone(this)) {
getBasket().takeStone(this);
siesta();
getBasket().putStone(this);
}
crossPass();
getBasket().takeStone(this);
}
}
}
Ниже приведен абстрактный класс со всеми методами, определенными при расширении потока.
общедоступный абстрактный класс Railway расширяет поток { имя частной строки; // имя железной дороги частная статическая корзина sharedBasket = новая корзина («общая корзина»); // общая // корзина для уведомлений частная корзина корзина; // частная корзина частная железнодорожная система railwaySystem; // система, которую формирует эта железная дорогачасть частной задержки задержки; // задержка, используемая этой железной дорогой
private Position position; // the position of the train on this railway
public Railway(String name,Delay delay) {
this.name = name;
this.delay = delay;
position = Position.END_PASS; // all trains start just after the
basket = new Basket(name "'s basket");
}
/**
* Register this railway with a railway system
* @param railwaySystem the railway system this railway must be registered with
*/
public void register(RailwaySystem railwaySystem) {
this.railwaySystem = railwaySystem;
}
/**
* Get the railway system this railway is registered with
* @return the railway system this railway is registered with
* @throws ProgrammingError if the railway is not registered
*/
public RailwaySystem getRailwaySystem() throws ProgrammingError {
if (railwaySystem == null) {
throw new ProgrammingError(name " is not registered with a railway
system");
}
return railwaySystem;
}
/**
* Get this railway's name
* @return this railway's name
*/
public String name() {
return name;
}
/**
* Get this railway's private basket.
* @return this railway's private basket
*/
public Basket getBasket() {
return basket;
}
/**
* Get the shared basket.
* @return the basket shared between all railways.
*/
public static Basket getSharedBasket() {
return sharedBasket;
}
/**
* Use delay to generate a delay for this railway
*/
public void delay() {
delay.delay();
}
// Fields keeping track of trains in the pass
private static int trainsInPass = 0; // how many trains are in the pass
/**
* Defines parts of the railway system. These are specified as:
* <ul>
* <li> START_PASS: just before entering the shared pass.</li>
* <li> IN_PASS: in the shared pass.</li>
* <li> END_PASS: at the end of the shared pass.</li>
* </ul>
* Trains start at the end of the pass, and must thereafter cycle through
* positions START_PASS, IN_PASS, END_PASS.
*/
public static enum Position {
START_PASS, IN_PASS, END_PASS;
public String toString() {
switch (this) {
case START_PASS: return "at the start of the pass";
case IN_PASS: return "in the pass";
case END_PASS: return "at the end of the pass";
default: return "at an undefined position on the railway (ERROR)";
}
}
}
/**
* Enter the pass.
* This method does <i>not</i> check if it is safe to enter the pass. It is
* merely for
* administration of the information about trains in the pass.
* @throws ProgrammingError if this railway thinks it already has a train in
* the pass.
*/
private synchronized void enterPass() throws ProgrammingError {
railwaySystem.trace(name ": entering pass");
if (position != Position.START_PASS) {
throw new ProgrammingError(name " cannot enter the pass, it is not "
Position.START_PASS ", it is " position ".");
}
position = Position.IN_PASS;
trainsInPass ;
}
/**
* Leave the pass.
* This method is merely for administration of the information about trains in
* the pass.
* @throws ProgrammingError if this railway thinks it does not have a train in
* the pass,
* or if there is no record of any trains in the pass.
*/
private synchronized void leavePass() throws ProgrammingError {
if (position != Position.IN_PASS) {
throw new ProgrammingError(name " cannot leave the pass, it is not "
Position.IN_PASS ", it is " position ".");
}
if (trainsInPass == 0) {
throw new ProgrammingError("There is no train to leave the pass (even
though " name " thinks it is in the pass.");
}
position = Position.END_PASS;
trainsInPass--;
railwaySystem.trace(name ": leaving pass");
}
/**
* Travel round the safe part of the railway (outside the pass).
* @throws ProgrammingError if the train is not currently at the end of the
* pass (and therefore at the start of the
* safe part of the railway).
*/
public void choochoo() throws ProgrammingError {
if (position != Position.END_PASS) {
throw new ProgrammingError(name " cannot traverse safe section, it is
not " Position.END_PASS ", it is " position ".");
}
railwaySystem.trace (name ": choo-choo");
delay();
position = Position.START_PASS;
}
/**
* Have a siesta.
*/
public void siesta() {
railwaySystem.trace(name ": zzzzz");
delay();
}
/**
* Cross the pass.
* @throws SafetyViolationError if there is/are already train(s) on the pass.
*/
public void crossPass() throws RailwaySystemError {
enterPass();
if (trainsInPass > 1) {
throw new SafetyViolationError("There are now " trainsInPass "
trains in the pass!");
}
railwaySystem.trace(name ": crossing pass");
delay();
leavePass();
}
// Error flag must be shared so that we can stop all railways if something goes
// wrong
private static boolean errorFlag = false;
protected static String errorMessage = "";
/**
* Run the railway.
*/
public void run() {
setErrorFlag(false);
try {
runTrain();
} catch (RailwaySystemError error) {
setErrorFlag(true);
errorMessage = error.getMessage();
System.out.println("!!! Something went wrong with the railway.nt"
errorMessage);
}
if (errorOccurred()) {
System.out.println("!!! " name() " shut down because of an
error.nt" errorMessage);
} else {
System.out.println(name() " shut down because time limit was
reached.");
}
}
/**
* Each railway should independently define how the trains are to be run, using
* the basket(s).
* to maintain safety on the pass.
* @throws RailwaySystemError if a safety violation occurs while the railway is
* being run.
*/
public abstract void runTrain() throws RailwaySystemError, RailwaySystemError;
/**
* Set the shared error flag (if an error occurs).
* @param errorFlag is true iff an error has occured.
*/
public static void setErrorFlag(boolean errorFlag) {
Railway.errorFlag = errorFlag;
}
/**
* Check the current error status.
* @return true iff an error is currently active.
*/
public static boolean errorOccurred() {
return errorFlag;
}
}
Наконец, я получил класс, который содержит основной метод.
общедоступный класс RailwaySystem {
private Clock clock = null; // the clock used to time railways - must be initialised for
// each run
private List<Railway> railways;
public RailwaySystem(List<Railway> railways,Clock clock) {
this.clock = clock;
this.railways = railways;
clock.register(this);
for (Railway railway: railways) {
railway.register(this);
}
}
/**
* Start the railway system
*/
private void start() {
clock.start();
for (Railway railway: railways) {
railway.start();
}
}
/**
* Wait for the system to stop
*/
private void stop() throws RailwaySystemError {
try {
clock.join();
for (Railway railway: railways) {
railway.join();
}
} catch (InterruptedException interrupt) {
throw new RailwaySystemError("The railway system was interrupted: "
interrupt.getMessage());
}
}
/**
* Given a railway, get the next one in the system's list
* @param railway the given railway
* @return the next railway in the list
* @throws ProgrammingError if railway is neither peru not bolivia
*/
public Railway getNextRailway(Railway railway) throws ProgrammingError {
int index = railways.indexOf(railway);
if (index == -1) { // railway is not in the list
throw new ProgrammingError(railway.name() " is not registered with this system");
}
return railways.get((index 1) % railways.size());
}
/**
* Get this system's clock
* @return the system's clock
* @throws SetUpError if the clock is not initialised
*/
public Clock getClock() throws SetUpError {
if (clock == null) {
throw new SetUpError("Clock has not been intialised");
}
return clock;
}
/**
* Provide a facility for switching tracing on/off.
**/
private boolean tracingOn = false;
/**
* Switch tracing on.
**/
public void traceOn() {
tracingOn = true;
}
/**
* Switch tracing off.
**/
public void traceOff() {
tracingOn = false;
}
/**
* Trace, if tracing is on
* @param trace the trace to be output
*/
public synchronized void trace(String trace) {
if (tracingOn) {
System.out.println(trace);
}
}
public static void main(String[] args) throws RailwaySystemError {
List<Railway> railways = new ArrayList<Railway>();
railways.add(new Peru());
railways.add(new Bolivia());
Clock clock = new Clock(1.0,20); // 20 ticks of 1 second
RailwaySystem system = new RailwaySystem(railways,clock);
system.traceOn();
system.start();
system.stop();
}
}
Мне нужно разрешить синхронизацию в моем методе runTrain (), это проблема, с которой я сейчас сталкиваюсь. Большое спасибо
Ответ №1:
Код, над которым вы работаете, содержит ошибки. Это… не очень хороший знак, ваш профессор / книга не понимает потоковую обработку, и все же они пытаются научить вас.
public void traceOff() {
tracingOn = false;
}
public synchronized void trace(String trace) {
if (tracingOn) {
System.out.println(trace);
}
}
В каждом потоке есть злая монета. Каждый раз, когда какой-либо поток обращается к любой переменной (для чтения или записи), этот поток переворачивает эту монету. Heads, и он будет использовать свою собственную локальную копию. Несмотря на то, что существует только одно поле, каждый поток имеет его кэшированную копию, и в заголовках поток обновляет / просматривает только свою локальную копию. Хвосты, и он использует / обновляет фактическое поле, а не копию.
Монета — это зло: это не «честная» монета, и сегодня она может каждый раз переворачивать хвосты, когда вы ее пишете и запускаете свои тесты. А затем, когда приходит важный клиент и вы демонстрируете свое приложение, монета все время переворачивается. Потому что ты просто не нравишься Мерфи.
Решение состоит в том, чтобы либо остановить виртуальную машину от подбрасывания монеты, либо обеспечить, чтобы ваш код не заботился о результате.
Единственный и единственный способ гарантировать, что виртуальная машина не перевернет ее, — это установить так называемое отношение «приходит до / приходит после» (CBCA) между строкой, которая задает поле, и строкой, которая считывает поле.
Другими словами, вышеупомянутая пара действий ( traceOff
и trace
) работает не так, как предполагалось, и приводит к переворачиванию этой злой монеты, если только отношение CBCA не настроено таким образом, что tracingOn = false
вызов «предшествует» if (tracingOn)
вызову. Это означает: учитывая сценарий, в котором поток A устанавливает трассировку в on, а 3 часа спустя поток B вызывает trace
, JVM разрешено действовать так, как если tracing
бы он был включен, и это была бы законная виртуальная машина. Он также может действовать так, как будто tracing
выключен, и это также было бы законно: JMM намеренно не дает никаких гарантий в любом случае. Очевидно, что это приложение с ошибками. Очевидно, что точка зрения должна заключаться в том, что трассировка здесь всегда включена. К сожалению, для этого требуется связь CBCA, и этот код этого не устанавливает. Итак, как это сделать?
synchronized
это один из способов сделать это, но здесь это сделано неправильно. Всякий раз, когда код выходит synchronized (x)
из блока, затем, когда другой код позже входит в synchronized (x)
блок (и из-за синхронизации эти события не могут происходить одновременно), весь код, выполняемый потоком 1, «предшествует» коду из потока 2.
Здесь этого не происходит: traceOff
метода synchronized
вообще нет, поэтому отношения CBCA не существует, поэтому этот код нарушен.
private static Basket sharedBasket = new Basket("shared basket");
Это обязательно должно быть final
.
Проблема на данный момент заключается в том, что мой текущий метод не позволяет синхронизировать одновременное прохождение двух поездов.
Это не кажется правильным. Ваш код пытается решить эту проблему; это, безусловно, то, о чем идет речь о том, чтобы класть камни в корзину:
getBasket().putStone(this);
while (nextRailway.getBasket().hasStone(this)) {
getBasket().takeStone(this);
siesta();
getBasket().putStone(this);
}
Этот код не имеет смысла — камень в корзине — это знак того, что на перевале есть другой поезд, поэтому, когда там есть камень, вы ждете, но берете камень! Это означало бы, что если появится третий поезд, бабах. Не прикасайтесь к камню. Оставьте это как есть.
Я предполагаю, что код для этой корзины предоставлен (не написан вами) и является потокобезопасным, но поскольку мы установили, что ваш prof / this book, похоже, не знает, как писать потокобезопасный код, это предположение, которое, как сказали бы википедисты, получает]. Предполагая, что код корзины является правильным и потокобезопасным, у вас есть все необходимые инструменты, но вы используете их неправильно.
Идея, без сомнения, заключается в том, что для пересечения перевала вы должны положить камень в корзину, но только если там еще нет камня, затем пересечь перевал, затем вытащить камень, чтобы указать, что вы закончили свое путешествие. Если вы не можете положить камень в корзину (потому что внутри уже есть камень), подождите некоторое время и повторите попытку позже. Не вводите проход, пока вам не удастся успешно положить камень в пустую корзину.
Здесь есть 2 важные реализации:
- Задачи «проверить, находится ли камень в корзине» и «положить камень в корзину» переплетены: вы не можете выполнять их последовательно; что, если оба поезда проверяют корзину, оба видят, что она пуста, а затем оба кладут камень, а затем оба входят в проход, а затемоба выходят из строя и умирают ужасной огненной смертью? Концепция «положить камень, но только если камня еще нет, и сообщить, удалось ли это» — это одна единственная операция, которую нельзя разделить — это называется атомарной операцией.
- Независимо от того, что или как, какой бы поезд ни положил камень в корзину, необходимо убедиться, что он удаляет камень, когда закончит, независимо от того, как поезд выходит из прохода.
Корзина должна поддерживать это, и неясно, поддерживает ли это. Завершается ли putStone
сбой метода, если в нем уже есть камень? Если это произойдет, как это произойдет? Возвращает ли он логическое false
значение или что-то выдает?
Если это не сработает (вы можете положить камни в корзину, даже если в ней уже есть камень), метод почти полностью бесполезен — вам нужно synchronize
что-то сделать, чтобы выполнить ваши требования к атомарности.
если это не удается, используйте это. Ваш алгоритм должен измениться на что-то вроде этого:
while (true) { // keep trying until we succeed.
boolean actuallyPlacedStone = basket.putStoneIfBasketEmpty(this);
if (!actuallyPlacedStone) {
// the basket is filled, we can't go.
siesta(); // sleep a while, and ...
continue; // start from the top
}
// if we got here, we placed the stone!
try {
crossThePass();
} finally {
// no matter how we get out of that pass, be it on rails
// or by tumbling down the mountain in a horrible accident...
// the pass is now clear, so, take the stone with you.
basket.takeStone(this);
}
break; // we're through, no need to repeat this process.
}
Если basket не дает никаких гарантий потока, я думаю, мы можем использовать его в качестве блокировки. ВСЕ взаимодействия с этой корзиной теперь должны быть в синхронизированном блоке, чтобы гарантировать, что никакие 2 потока не действуют на корзину одновременно, а также установить CBCA повсюду и не допустить, чтобы злая монета испортила наш день. Я бы создал вспомогательные методы:
public boolean placeStoneIfEmpty(Basket b) {
synchronized (b) {
if (b.hasStone()) return false;
b.putStone(this);
return true;
}
}
public void removeStone(Basket b) {
synchronized (b) {
b.removeStone(this);
}
}
Комментарии:
1. Я действительно ценю ваш ответ. Синхронизация должна произойти, чтобы избежать двух поездов, входящих в проход. Это была моя ошибка, извините.
2.
a so-called 'comes before/comes after' (CBCA) relationship
Спецификация языка Java не называет это так. Он использует happens-before. (И для действия, которое вызывает это, он синхронизируется с. ) Ваша терминология для меня нова, я никогда не слышал, чтобы кто-нибудь использовал ее за 15 лет программирования на Java. (хотя «Злая монета» — отличная аналогия.)3. Да, я понял — этот ответ охватывает все детали, которые вам нужны: [1] то, что код, над которым вы работаете, является, мягко говоря, подозрительным, [2] что вам нужно сделать, предполагая, что в корзине есть то, что вам нужно (в частности, атомарный «положить в корзину, но только если этопустой’), и [3] что вам нужно сделать, если в корзине этого нет. С помощью надлежащих операций в этой корзине вы можете тривиально гарантировать, что ни один поезд не войдет в проход, если другой уже там.
4. Мне также нужно, чтобы алгоритм также заканчивался в какой-то момент!! Приведенный ниже код также должен быть включен! Clock clock = getRailwaySystem().getClock(); Железнодорожный nextRailway = getRailwaySystem().getNextRailway(это);