Part 2 recap: reflection-built events & thread-safe concurrency
The advanced half: build Event subclasses by name with reflection (so new events drop in without recompiling the controller), make Event implement Runnable to run one thread per event, suspend/resume the thread collection cooperatively, and guard shared state with a synchronized setVariable.
Part 2: make the framework open-ended and concurrent
Part 1 still hard-references each event class (new LightOn(t)). Part 2 removes even that coupling with reflection, then runs every event on its own thread with proper synchronization.
### 1. Build events by name with reflection
The parser already extracts the event-name string ("LightOn"). Instead of a giant if/else mapping names to constructors, Part 2 loads the class by name and constructs it reflectively (Module 7):
String fqn = "GreenhouseControls$" + name; // inner class binary name
Class<?> c = Class.forName(fqn);
Constructor<?> ctor = c.getConstructor(GreenhouseControls.class, long.class);
Event e = (Event) ctor.newInstance(outer, time);
Note the inner-class detail: an inner class's constructor takes a hidden first parameter — the enclosing GreenhouseControls instance. Its binary name uses $ (GreenhouseControls$LightOn). With reflection in place, adding a new event type needs no controller recompile — drop in the class and name it in the file. The exam phrases this as *"how do you add an event class without changing/recompiling the controller?"* → reflection.
### 2. Event implements Runnable — one thread per event
In Part 2 each event runs concurrently: Event is made implements Runnable, its old timing/action logic moves into run(), and the controller starts a thread per event (Module 10):
public abstract class Event implements Runnable {
public void run() {
// wait until ready, then perform the action
action();
}
}
// start it:
new Thread(event).start();
The canonical mistake: calling event.run() directly executes it on the *current* thread — no concurrency. You must call new Thread(event).start() to get a new thread.
### 3. Suspend / resume the running collection
The control panel can pause and continue everything. The *deprecated* Thread.suspend()/resume() deadlock-prone APIs are avoided; instead each event watches a cooperative flag and uses a guarded block (wait/notifyAll, Module 10.4):
private volatile boolean paused = false;
public synchronized void pause() { paused = true; }
public synchronized void unpause() { paused = false; notifyAll(); }
public synchronized void awaitResume() throws InterruptedException {
while (paused) wait(); // guarded block: re-check in a loop
}
### 4. Synchronize the shared state
Now that many threads can mutate greenhouse state at once, every write must be a critical section. The project funnels writes through one guarded mutator that updates a shared collection of TwoTuple name/value pairs (the generic TwoTuple<A,B> from Module 6):
private List<TwoTuple<String,Boolean>> state = new ArrayList<>();
public synchronized void setVariable(String name, boolean value) {
state.add(new TwoTuple<String,Boolean>(name, value));
}
synchronized makes the read-modify-write of state atomic with respect to other synchronized callers — without it, two threads adding at once can corrupt the ArrayList or lose an update. This is the course's canonical race-on-shared-state lesson.