Monitor.java

package nl.tudelft.simulation.language.concurrent;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Monitor class. In the Java programming language there is a lock associated with every object. The language does not provide a
 * way to perform separate lock and unlock operations; instead, they are implicitly performed by high-level constructs that
 * always arrange to pair such operations correctly. This Monitor class, however, provides separate monitorenter and monitorexit
 * instructions that implement the lock and unlock operations. The class is final for now, as it is not the idea that the class
 * should be extended. It has only static methods.
 * <p>
 * Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
 * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
 * project is distributed under a three-clause BSD-style license, which can be found at
 * <a href="https://https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">
 * https://https://simulation.tudelft.nl/dsol/docs/latest/license.html</a>.
 * </p>
 * @author <a href="mailto:phmjacobs@hotmail.com">Peter H.M. Jacobs</a>
 * @author <a href="http://tudelft.nl/averbraeck">Alexander Verbraeck</a>
 */
public final class Monitor
{
    /** the locks held. */
    private static Map<Object, MonitorThread> locks = new LinkedHashMap<>();

    /**
     * constructs a new Monitor.
     */
    private Monitor()
    {
        // unreachable code
    }

    /**
     * locks an object for the current thread.
     * @param object Object; the object to lock
     */
    public static void lock(final Object object)
    {
        Monitor.lock(object, Thread.currentThread());
    }

    /**
     * locks an object for the given requestor.
     * @param object Object; the object to lock.
     * @param requestor Thread; the requesting thread.
     */
    public static void lock(final Object object, final Thread requestor)
    {
        synchronized (Monitor.locks)
        {
            if (Monitor.get(object) == null)
            {
                Monitor.locks.put(object, new MonitorThread(requestor, object));
            }
            else
            {
                MonitorThread thread = Monitor.get(object);
                if (thread.getOwner().equals(requestor))
                {
                    thread.increaseCounter();
                }
                else
                {
                    synchronized (object)
                    {
                        // We wait until we gained access to the monitor
                        Monitor.locks.put(object, new MonitorThread(requestor, object));
                    }
                }
            }
        }
    }

    /**
     * unlocks an object locked by the current Thread.
     * @param object Object; the object to unlock
     */
    public static void unlock(final Object object)
    {
        Monitor.unlock(object, Thread.currentThread());
    }

    /**
     * unlocks an object locked by owner.
     * @param object Object; the object to unlock.
     * @param owner Thread; the owning thread.
     */
    public static void unlock(final Object object, final Thread owner)
    {
        synchronized (Monitor.locks)
        {
            MonitorThread thread = Monitor.get(object);
            if (thread == null)
            {
                throw new IllegalMonitorStateException("object(" + object + ") is not locked");
            }
            if (!thread.getOwner().equals(owner))
            {
                throw new IllegalMonitorStateException(owner + " cannot" + " unlock object owned by " + thread.getOwner());
            }
            thread.decreaseCounter();
            if (thread.getCounter() == 0)
            {
                thread.interrupt();
                Monitor.locks.remove(object);
            }
        }
    }

    /**
     * returns the MonitorThread for a specific key.
     * @param key Object; the key to resolve
     * @return the MonitorThread
     */
    private static MonitorThread get(final Object key)
    {
        return locks.get(key);
    }

    /**
     * A MonitorThread is used to lock an object.
     */
    private static class MonitorThread extends Thread
    {
        /** the monitor to use. */
        private Object object = null;

        /** the owning thread. */
        private Thread owner = null;

        /** the counter. */
        private int counter = 0;

        /**
         * constructs a new MonitorThread.
         * @param owner Thread; the owning thread
         * @param object Object; the object
         */
         MonitorThread(final Thread owner, final Object object)
        {
            super("MonitorThread on " + object.getClass());
            this.setDaemon(true);
            this.owner = owner;
            synchronized (object)
            {
                this.object = object;
                increaseCounter();
                this.start();
            }
            synchronized (owner)
            {
                try
                {
                    this.owner.wait();
                }
                catch (InterruptedException exception)
                {
                    exception = null;
                    /*
                     * This interrupted exception is thrown because this monitor thread has started and interrupted its
                     * constructor. We now know object is locked and may therefore return.
                     */
                }
            }
        }

        /**
         * @return Returns the counter.
         */
        public synchronized int getCounter()
        {
            return this.counter;
        }

        /**
         * decreases the counter with one.
         */
        public synchronized void decreaseCounter()
        {
            this.counter = Math.max(0, this.counter - 1);
        }

        /**
         * increases the counter of this thread with one.
         */
        public synchronized void increaseCounter()
        {
            this.counter++;
        }

        /**
         * @return Returns the owning thread.
         */
        public Thread getOwner()
        {
            return this.owner;
        }

        /** {@inheritDoc} */
        @Override
        public void run()
        {
            try
            {
                // We lock the object
                synchronized (this.object)
                {
                    // Since we have locked the object, we can now return
                    // the constructor
                    this.owner.interrupt();

                    // We join
                    this.join();
                }
            }
            catch (Exception exception)
            {
                // This is OK.. We use this construction in the
                // MonitorTest.unlock to release a lock
                exception = null;
            }
        }
    }
}