Repository navigation
Expand file tree
/
Copy pathFerryThread.java
More file actions
223 lines (192 loc) · 10.5 KB
/
Copy pathFerryThread.java
File metadata and controls
223 lines (192 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package ferrysimulation;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The ferry thread.
*
* ── Synchronization Design ────────────────────────────────────────────────
*
* Loading (critical section):
* The ferry peeks at the front of the FIFO queue on the current side,
* decides if the vehicle fits, then dequeues it and issues a semaphore
* permit so only that one vehicle may board. It then blocks on
* vehicle.boardedSemaphore until boarding is acknowledged. This makes
* the loading of each vehicle mutually exclusive and strictly FIFO.
*
* Departure policy:
* The ferry departs when:
* (a) ferry is full (20 units),
* (b) no waiting vehicle can fit in remaining capacity,
* (c) MAX_WAIT_MS elapsed since start of loading (starvation prevention),
* (d) opposite side has FAIRNESS_THOLD more vehicles (fairness).
*
* Crossing & unloading:
* After crossing, ferry releases vehicle.arrivalSemaphore for each
* passenger. After a brief unload delay it releases
* vehicle.unloadedSemaphore. No boarding may happen during unloading
* because the ferry only starts its next loading loop after unloading.
*
* Deadlock avoidance:
* • Lock ordering: WaitingArea monitor → per-vehicle semaphores.
* The ferry never holds the area lock while waiting on a semaphore.
* • Vehicles never hold any lock while waiting.
* • All waits are bounded (semaphores, no infinite blocking without
* a release path).
*/
public class FerryThread extends Thread {
static final int MAX_CAPACITY = 20;
static final int MAX_WAIT_MS = 800;
static final int FAIRNESS_THOLD = 3;
static final int CROSS_TIME_MS = 400;
static final int LOAD_DELAY_MS = 50;
private final WaitingArea[] areas;
private int currentSide;
private final int totalVehicles;
private final AtomicInteger completedVehicles = new AtomicInteger(0);
private final CountDownLatch allDoneLatch;
// statistics
private int tripCount = 0;
private long totalUnitsCarried = 0;
// ─────────────────────────────────────────────────────────────────────────
public FerryThread(WaitingArea[] areas, int startingSide,
int totalVehicles, CountDownLatch allDoneLatch) {
super("Ferry");
this.areas = areas;
this.currentSide = startingSide;
this.totalVehicles = totalVehicles;
this.allDoneLatch = allDoneLatch;
SimLogger.log("Ferry created – starting on " + Vehicle.sideName(startingSide));
}
// ─────────────────────────────────────────────────────────────────────────
@Override
public void run() {
try {
while (completedVehicles.get() < totalVehicles) {
doTrip();
}
SimLogger.log("Ferry: all vehicles completed – shutting down.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// ─────────────────────────────────────────────────────────────────────────
private void doTrip() throws InterruptedException {
WaitingArea area = areas[currentSide];
List<Vehicle> passengers = new ArrayList<>();
int loadedUnits = 0;
long loadStart = System.currentTimeMillis();
SimLogger.log("Ferry is on " + Vehicle.sideName(currentSide)
+ " – starting to load...");
// ── LOADING PHASE ─────────────────────────────────────────────────────
loadingLoop:
while (true) {
// Brief wait for vehicles to appear
area.waitForVehicle(200);
// Check departure conditions
if (!passengers.isEmpty() && shouldDepart(area, loadedUnits, loadStart)) {
break;
}
Vehicle next = area.peek();
if (next == null) {
// No vehicle yet; check if we should give up waiting
if (completedVehicles.get() >= totalVehicles) break;
// Cross to other side if it has vehicles and we have waited long
int oppSize = areas[1 - currentSide].size();
if (oppSize > 0 && System.currentTimeMillis() - loadStart > MAX_WAIT_MS) {
SimLogger.log("Ferry: switching side for fairness (other side has "
+ oppSize + " vehicles).");
break;
}
continue;
}
int needed = next.getType().capacity;
if (loadedUnits + needed > MAX_CAPACITY) {
// Front vehicle doesn't fit; FIFO means we cannot load anyone else.
// Depart if we already have passengers.
if (!passengers.isEmpty()) {
SimLogger.log("Ferry: front vehicle doesn't fit – departing.");
break loadingLoop;
}
// No passengers yet and front doesn't fit – shouldn't happen
// (max vehicle = 3 units, capacity = 20), but handle safely.
Thread.sleep(100);
continue;
}
// ── Board this vehicle (critical section) ─────────────────────────
Vehicle v = area.dequeue(); // removes from FIFO queue
if (v == null) continue; // edge case: taken by another thread (not possible here)
// Release boarding permit → vehicle wakes up and boards
v.boardSemaphore.release();
// Wait until the vehicle signals it has boarded
v.boardedSemaphore.acquire();
loadedUnits += needed;
passengers.add(v);
totalUnitsCarried += needed;
SimLogger.log("Ferry loaded " + v.getVehicleName()
+ " (load = " + loadedUnits + "/" + MAX_CAPACITY + ")");
Thread.sleep(LOAD_DELAY_MS);
}
// ── DEPARTURE ─────────────────────────────────────────────────────────
if (passengers.isEmpty()) {
// If nothing to carry, just switch sides or wait
int oppSize = areas[1 - currentSide].size();
if (oppSize > 0) {
SimLogger.log("Ferry: crossing empty to serve "
+ Vehicle.sideName(1 - currentSide));
currentSide = 1 - currentSide;
Thread.sleep(CROSS_TIME_MS);
SimLogger.log("Ferry arrived (empty) at " + Vehicle.sideName(currentSide));
} else {
Thread.sleep(100);
}
return;
}
tripCount++;
SimLogger.log("Ferry departed from " + Vehicle.sideName(currentSide)
+ " with " + passengers.size() + " vehicle(s)"
+ " [" + loadedUnits + " units] – trip #" + tripCount);
// ── CROSSING ──────────────────────────────────────────────────────────
Thread.sleep(CROSS_TIME_MS);
currentSide = 1 - currentSide;
SimLogger.log("Ferry arrived at " + Vehicle.sideName(currentSide));
// ── UNLOADING (no new boarding until complete) ─────────────────────────
for (Vehicle v : passengers) {
v.arrivalSemaphore.release(); // wake passengers: we've arrived
}
Thread.sleep(60); // unloading delay
for (Vehicle v : passengers) {
v.unloadedSemaphore.release(); // passengers may proceed
}
Thread.sleep(60);
SimLogger.log("Ferry: unloading complete on " + Vehicle.sideName(currentSide));
}
// ─────────────────────────────────────────────────────────────────────────
private boolean shouldDepart(WaitingArea area, int loadedUnits, long loadStart) {
if (loadedUnits >= MAX_CAPACITY) return true;
long elapsed = System.currentTimeMillis() - loadStart;
if (elapsed >= MAX_WAIT_MS) return true;
int oppSize = areas[1 - currentSide].size();
int thisSize = area.size();
if (oppSize - thisSize >= FAIRNESS_THOLD) {
SimLogger.log("Ferry: fairness triggered (opp=" + oppSize
+ " this=" + thisSize + ") – departing.");
return true;
}
return false;
}
// ── Callbacks ─────────────────────────────────────────────────────────────
public void vehicleCompleted() {
int done = completedVehicles.incrementAndGet();
SimLogger.log("Progress: " + done + "/" + totalVehicles + " vehicles done.");
if (done >= totalVehicles) allDoneLatch.countDown();
}
// ── Statistics ─────────────────────────────────────────────────────────────
public int getTripCount() { return tripCount; }
public long getTotalUnitsCarried() { return totalUnitsCarried; }
}