1 package nl.tudelft.simulation.language.concurrent;
2
3 import java.util.LinkedHashMap;
4 import java.util.Map;
5
6 /**
7 * Monitor class. In the Java programming language there is a lock associated with every object. The language does not provide a
8 * way to perform separate lock and unlock operations; instead, they are implicitly performed by high-level constructs that
9 * always arrange to pair such operations correctly. This Monitor class, however, provides separate monitorenter and monitorexit
10 * instructions that implement the lock and unlock operations. The class is final for now, as it is not the idea that the class
11 * should be extended. It has only static methods.
12 * <p>
13 * Copyright (c) 2002-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
14 * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
15 * project is distributed under a three-clause BSD-style license, which can be found at
16 * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
17 * </p>
18 * @author <a href="mailto:phmjacobs@hotmail.com">Peter H.M. Jacobs</a>
19 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
20 */
21 public final class Monitor
22 {
23 /** the locks held. */
24 private static Map<Object, MonitorThread> locks = new LinkedHashMap<>();
25
26 /**
27 * constructs a new Monitor.
28 */
29 private Monitor()
30 {
31 // unreachable code
32 }
33
34 /**
35 * locks an object for the current thread.
36 * @param object the object to lock
37 */
38 public static void lock(final Object object)
39 {
40 Monitor.lock(object, Thread.currentThread());
41 }
42
43 /**
44 * locks an object for the given requestor.
45 * @param object the object to lock.
46 * @param requestor the requesting thread.
47 */
48 public static void lock(final Object object, final Thread requestor)
49 {
50 synchronized (Monitor.locks)
51 {
52 if (Monitor.get(object) == null)
53 {
54 Monitor.locks.put(object, new MonitorThread(requestor, object));
55 }
56 else
57 {
58 MonitorThread thread = Monitor.get(object);
59 if (thread.getOwner().equals(requestor))
60 {
61 thread.increaseCounter();
62 }
63 else
64 {
65 synchronized (object)
66 {
67 // We wait until we gained access to the monitor
68 Monitor.locks.put(object, new MonitorThread(requestor, object));
69 }
70 }
71 }
72 }
73 }
74
75 /**
76 * unlocks an object locked by the current Thread.
77 * @param object the object to unlock
78 */
79 public static void unlock(final Object object)
80 {
81 Monitor.unlock(object, Thread.currentThread());
82 }
83
84 /**
85 * unlocks an object locked by owner.
86 * @param object the object to unlock.
87 * @param owner the owning thread.
88 */
89 public static void unlock(final Object object, final Thread owner)
90 {
91 synchronized (Monitor.locks)
92 {
93 MonitorThread thread = Monitor.get(object);
94 if (thread == null)
95 {
96 throw new IllegalMonitorStateException("object(" + object + ") is not locked");
97 }
98 if (!thread.getOwner().equals(owner))
99 {
100 throw new IllegalMonitorStateException(owner + " cannot" + " unlock object owned by " + thread.getOwner());
101 }
102 thread.decreaseCounter();
103 if (thread.getCounter() == 0)
104 {
105 thread.interrupt();
106 Monitor.locks.remove(object);
107 }
108 }
109 }
110
111 /**
112 * returns the MonitorThread for a specific key.
113 * @param key the key to resolve
114 * @return the MonitorThread
115 */
116 private static MonitorThread get(final Object key)
117 {
118 return locks.get(key);
119 }
120
121 /**
122 * A MonitorThread is used to lock an object.
123 */
124 private static class MonitorThread extends Thread
125 {
126 /** the monitor to use. */
127 private Object object = null;
128
129 /** the owning thread. */
130 private Thread owner = null;
131
132 /** the counter. */
133 private int counter = 0;
134
135 /**
136 * constructs a new MonitorThread.
137 * @param owner the owning thread
138 * @param object the object
139 */
140 MonitorThread(final Thread owner, final Object object)
141 {
142 super("MonitorThread on " + object.getClass());
143 this.setDaemon(true);
144 this.owner = owner;
145 synchronized (object)
146 {
147 this.object = object;
148 increaseCounter();
149 this.start();
150 }
151 synchronized (owner)
152 {
153 try
154 {
155 this.owner.wait();
156 }
157 catch (InterruptedException exception)
158 {
159 exception = null;
160 /*
161 * This interrupted exception is thrown because this monitor thread has started and interrupted its
162 * constructor. We now know object is locked and may therefore return.
163 */
164 }
165 }
166 }
167
168 /**
169 * @return Returns the counter.
170 */
171 public synchronized int getCounter()
172 {
173 return this.counter;
174 }
175
176 /**
177 * decreases the counter with one.
178 */
179 public synchronized void decreaseCounter()
180 {
181 this.counter = Math.max(0, this.counter - 1);
182 }
183
184 /**
185 * increases the counter of this thread with one.
186 */
187 public synchronized void increaseCounter()
188 {
189 this.counter++;
190 }
191
192 /**
193 * @return Returns the owning thread.
194 */
195 public Thread getOwner()
196 {
197 return this.owner;
198 }
199
200 @Override
201 public void run()
202 {
203 try
204 {
205 // We lock the object
206 synchronized (this.object)
207 {
208 // Since we have locked the object, we can now return
209 // the constructor
210 this.owner.interrupt();
211
212 // We join
213 this.join();
214 }
215 }
216 catch (Exception exception)
217 {
218 // This is OK.. We use this construction in the
219 // MonitorTest.unlock to release a lock
220 exception = null;
221 }
222 }
223 }
224 }