View Javadoc
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 }