Your task is to implement a simulation of tunnels and vehicles using Java Threads. There will be few tunnels, but many vehicles. There will be several constraints on how many vehicles of each type a tunnel can contain at any given time. After you implement this primary system, you will implement a higher-level tunnel controller that will schedule access to the tunnels based on a priority system.
The project is split into 2 required tasks and 1 extra-credit task. Task 1 requires you to use the synchronized keyword to prevent race conditions. Task 2 requires you to build on your synchronized code and add monitors to avoid busy-waiting and add a scheduler. The extra credit task asks you to use monitors to make the scheduler preemptive.
Task 1 has an earlier due date than Task 2 and the extra-credit assignment. See LATTE for due dates.
Any implementation of the Tunnel interface must satisfy the following properties:
As you know from learning about concurrency, without proper synchronization, these constraints could be violated since a thread may secure permission to enter a tunnel, then be interrupted before it can actually enter, at which point another thread may also secure permission to enter the tunnel and do so. When the first thread to get permission to enter the tunnel does so, one of the invariants may be violated (e.g., maybe a sled and car will be in the tunnel at the same time).
Part of your job in this assignment is to use synchronization to prevent such race conditions from occurring regardless of scheduling.
You must use and may
You will need to understand how all provided code works to solve this problem, even though you will not be allowed to modify it in tasks 1 and 2. You will want to inspect the java files yourself, but to get you started here are the API docs generated with javadoc from the files provided with PA2.
Here is an example of running a Car in a thread that can enter a single tunnel. This example will not work until you have written the BasicTunnel class:
public class TestCar {
public static void main(String[] args) {
Tunnel tunnel = new BasicTunnel("0"); // you must write BasicTunnel
new Thread(new Car("0-Car", Direction.NORTH, tunnel)).start();
}
}
This simple example should output the following given a correct
implementation of BasicTunnel:
0-Car has entered 0 traveling in direction NORTH with priority 0 0-Car has exited 0 traveling in direction NORTH with priority 0
In PA1, you were instructed to consider using a synchronized class provided by Java for inter-thread communication (ArrayBlockingQueue) to solve the problem. For this project, that is not allowed; you may not use any synchronized data structure included in the Java API. You must write your own (using the "synchronized" keyword). Of course, you can and should use non-synchronized data structures in the Java standard library. You can consult the API docs to see if a data structure is synchronized.
You also may not use the thread priority methods provided by Java (e.g., you may not use Thread.setPriority).
You must create a class called BasicTunnel (in a file called BasicTunnel.java) that implements the interface Tunnel, and then within that class carry out the following tasks:
<vehicle-name> has entered <tunnel-name> traveling in direction <d> with priority <p>
<vehicle-name> has exited <tunnel-name> traveling in direction <d> with priority <p>
You are required to implement (and turn in as part of your project) a class called Test1 (in Test1.java) with a main method (so it can be run from the command line) which carries out the following test:
You have now successfully programmed BasicTunnel which enforces entry criteria and prevents race conditions. However, there are two problems with the design of BasicTunnel:
You must create a class called PriorityScheduler (in PriorityScheduler.java) that implements Tunnel and controls access to a collection of Tunnels in order to implement the priority scheduling policy described above. The Tunnels "behind" the PriorityScheduler will be BasicTunnels. BasicTunnel should be the same class you implemented for the first part of this project.
PriorityScheduler will carry out the following tasks:
You are required to implement (and turn in as part of your project) a class called Test2 (in Test2.java) with a main method (so it can be run from the command line) which carries out the following test:
This task is not required. If you choose to do it, it will be worth 10% extra credit. Please do not attempt it until you are confident that tasks 1 and 2 are implemented correctly. Please run your design idea by the TA before you start coding the extra credit portion.
Your extra credit task is to modify your scheduler to be preemptive. In order to do this, you are allowed to modify the doWhileInTunnel method and the constructors of the Vehicle class. The new scheduler class must be called PreemptivePriorityScheduler.
You are responsible for writing a test which exercises . Be sure to include enough simultaneous Ambulance threads that sometimes an Ambulance will have to wait.
You might find the instanceof operator (scroll down a bit on the page that links to) helpful. instanceof can tell you if an object can be downcast from a parent type into a child type. Typically, we don't use it because there are better techniques (polymorphism leverages the type system to do the work for you), but for this problem it will probably help you out.
Here is an example:
public class TestInstanceof {
public static class Parent {
}
public static class Child1 extends Parent {
}
public static class Child2 extends Parent {
}
public static void main(String[] args) {
Parent p1 = new Child1();
Parent p2 = new Child2();
if(p1 instanceof Child1) {
System.out.println("p1 is instance of Child1");
}
if(p1 instanceof Child2) {
System.out.println("p1 is instance of Child2");
}
if(p2 instanceof Child1) {
System.out.println("p2 is instance of Child1");
}
if(p2 instanceof Child2) {
System.out.println("p2 is instance of Child2");
}
}
}
This outputs:
p1 is instance of Child1 p2 is instance of Child2
The following two uses of the synchronized keyword are equivalent:
public synchronized void m() {
// ...
}
public void m() {
synchronized(this) {
// ...
}
}
If you want to synchronize the whole method on the lock contained
inside of "this", then it is more elegant to put the
synchronized keyword in the signature of the method.
Sometimes, you want to synchronize on smaller blocks of code than an entire method. This allows you to limit the amount of synchronization, which can improve performance by maximizing concurrency. In this case, putting synchronized in its own block can be the better choice.
public void m() {
// a non-critical section
synchronized(this) {
// a critical section
}
// a non-critical section
synchronized(this) {
// a critical section
}
// a non-critical section
}
Note that you can synchronize on the lock in any instance, not just "this". For example:
public class C {
private static Object o = new Object();
public void m() {
synchronized(o) {
// ...
}
}
}
You might not need to synchronize on an instance other than
"this" for this project.
Put all files into a folder named with your unet id (i.e., your email address without the "@brandeis.edu" part). Put that folder into a .zip or .tar.gz archive. Upload the archive to LATTE.