0

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.

enter image description here

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.

enter image description here

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.

0lt
  • 283
  • 2
  • 13
  • Your `Observer.run()` method keeps `Platform.lock()` locked almost 100% of the time. It unlocks the lock once every two seconds, but the very next thing it does is re-lock the lock, which makes it extremely unlikely that any other thread will be able to acquire the lock. – Solomon Slow Dec 13 '18 at 20:17
  • I bet that `yield()` does not do what you think it does. (Hint: In most situations, it is perfectly acceptable for `yield()` to do _nothing at all_.) – Solomon Slow Dec 13 '18 at 20:21
  • @SolomonSlow I thought this would increase the chance this thread will give up its time slice and the scheduler will schedule another one. – 0lt Dec 13 '18 at 20:22
  • 1
    `yield()`, _might_ do that, or it might do nothing, but it won't unlock the lock in either case. – Solomon Slow Dec 13 '18 at 20:23
  • If I replace `yield()` with `wait()` that does unlock the lock, I get an `IllegalMonitorStateException`, although `wait()` is in the block synchronized on the same lock. – 0lt Dec 14 '18 at 15:14
  • 1
    `wait()` means `this.wait()`, but `this` is not the lock object. You could use `Platform.lock.wait()` instead, but then that opens the question, what is it waiting for? Maybe you could `Platform.lock.wait(2000)` instead of `sleep(2000)`, though IMO, that's a bit of a hack. – Solomon Slow Dec 14 '18 at 16:29
  • @SolomonSlow silly mistake, fixed it and it worked. Thanks for helping me understand what was going on here. – 0lt Dec 14 '18 at 17:41

0 Answers0