I was learning about the old-school Java synchronization mechanisms and stumbled upon some problems. Code available on GitHub.
Consider the following test example.
From the main method, we construct a garage Platform
object, represented by a char matrix. There are two cars already parked (denoted with x) and a vehicle standing in the way (denoted with v). Cars are entering the garage from position (1,0) and driving through, as shown in red, looking right and left for an available parking spot.
From the main method, thirty vehicles are created (threads) and the start()
method is called. If there is a car in front of them blocking their way, they must call wait()
on the shared Platform.lock
object.
The Observer
class is in charge of printing the current state of the garage to the console.
Where problems occur is with the MysteryVehicle
thread, whose only job is to remove the obstacle (denoted with v) and let the cars pass in an orderly fashion. It seems that this object never acquires the lock's monitor.
Main class
package garage;
import garage.model.*;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
Platform platform = new Platform();
Vehicle.platform = platform;
platform.print();
Vehicle[] vehicles = new Vehicle[30];
for (int i = 0; i < 30; i++) {
vehicles[i] = new Vehicle();
}
for (int i = 0; i < 30; i++) {
vehicles[i].start();
}
Observer observer = new Observer();
observer.platform = platform;
observer.setDaemon(true);
observer.start();
MysteryVehicle mysteryVehicle = new MysteryVehicle();
mysteryVehicle.start();
try {
mysteryVehicle.join();
} catch (Exception exception) {
exception.printStackTrace();
}
try {
for (int i = 0; i < 30; i++)
vehicles[i].join();
} catch (Exception exception) {
exception.printStackTrace();
}
platform.print();
System.out.println("END");
System.out.println(platform.flag); // checks whether wait() was called anytime
}
public static void main(String[] args) {
launch(args);
}
}
Platform class
package garage.model;
public class Platform {
public boolean flag = false; // indicades whether wait() was called anytime
public char[][] fields = new char[10][8];
public static final Object lock = new Object();
public Platform() {
for (int i = 0; i < 10; i++)
for (int j = 0; j < 8; j++)
fields[i][j] = '.';
fields[1][0] = fields[2][0] = 'x'; // already parked cars
fields[4][1] = 'v'; // obstacle
}
public void print() {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 8; j++) {
System.out.print(fields[i][j]);
}
System.out.println();
}
System.out.println();
}
}
}
Vehicle class
package garage.model;
public class Vehicle extends Thread {
int x = 1;
int y = 0;
private int block = 1;
public static Platform platform;
public boolean moving = true;
public void run() {
y++; // entrance
while (moving) {
if (block == 1) {
while (moving && platform.fields[x + 1][y] == 'v')
try {
synchronized (Platform.lock) {
Platform.lock.wait();
platform.flag = true;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Platform.lock) {
x++;
Platform.lock.notifyAll();
yield(); // suggest another vehicle should get processor time
}
// looking left and right for an available spot
if (platform.fields[x][0] != 'x' && platform.fields[x][0] != '*') {
platform.fields[x][0] = '*'; // park
moving = false;
} else if (x < 8 && platform.fields[x][3] != 'x' && platform.fields[x][3] != '*'
&& platform.fields[x][2] != 'v') {
platform.fields[x][3] = '*';// park
moving = false;
}
// checking if we reached the end
if (moving && x == 9) {
block = 2;
move(); // transfer to the second block of the garage
}
} // end block 1
if (block == 2) {
// looking left and right for an available spot
if (platform.fields[x][7] != 'x' && platform.fields[x][7] != '*') {
platform.fields[x][7] = '*'; // park
moving = false;
} else if (x < 8 && platform.fields[x][4] != 'x' && platform.fields[x][4] != '*'
&& platform.fields[x][5] != 'v') {
platform.fields[x][4] = '*'; // park
moving = false;
}
while (moving && platform.fields[x - 1][y] == 'v')
try {
synchronized (Platform.lock) {
Platform.lock.wait(); // waiting for the mystery vehicle to disappear
platform.flag = true;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (moving) {
synchronized (Platform.lock) {
x--;
Platform.lock.notifyAll();
yield();
}
}
if (x == 1) {
y++;
moving = false;
}
} // end block 2
} // end moving
}
private void move() {
while (y != 6) {
if (y + 1 == 'v')
try {
synchronized (Platform.lock) {
platform.flag = true;
Platform.lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Platform.lock) {
y++;
Platform.lock.notifyAll();
yield();
}
}
}
}
Observer class
package garage.model;
public class Observer extends Thread {
public Platform platform;
{
setPriority(MIN_PRIORITY);
}
@Override
public void run() {
while (true) {
synchronized (Platform.lock) {
try {
sleep(2000);
platform.print();
yield();
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
Platform.lock.notifyAll();
}
}
}
}
}
MysteryVehicle class
package garage.model;
public class MysteryVehicle extends Vehicle {
{
setPriority(MAX_PRIORITY);
}
@Override
public void run() {
synchronized (Platform.lock) {
System.out.println("And the vehicle disappears!");
platform.fields[4][1] = '.';
}
}
}
After a while, Observer
produces the following output which never changes. It seems the MysteryVehicle never got processor time and all of the cars are stuck.
What am I missing here?
UPDATE:
Since yield()
doesn't release the lock, I tried to use wait()
instead - and got IllegalMonitorStateException
, even though wait()
is inside the block synchronized on the same lock!
I tried changing Observer
into a TimerTask
, scheduled by a Timer
for every 5 seconds. The output was way faster than that and the program wouldn't terminate.