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