diff --git a/monitor-object/pom.xml b/monitor-object/pom.xml new file mode 100644 index 000000000..011300547 --- /dev/null +++ b/monitor-object/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.20.0-SNAPSHOT + + monitor-object + monitor-object + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/AbstractMonitor.java b/monitor-object/src/main/java/com/iluwatar/monitor/AbstractMonitor.java new file mode 100644 index 000000000..dd1b512e9 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/AbstractMonitor.java @@ -0,0 +1,214 @@ +package com.iluwatar.monitor; + +import java.util.ArrayList; + +/** + * A class for Monitors. Monitors provide coordination of concurrent threads. + * Each Monitor protects some resource, usually data. At each point in time a + * monitor object is occupied by at most one thread. + */ +public abstract class AbstractMonitor { + final Semaphore entrance = new Semaphore(1); + volatile Thread occupant = null; + private final ArrayList listOfListeners = new ArrayList<>(); + private final String name; + + public String getName() { + return name; + } + + protected AbstractMonitor() { + this(null); + } + + protected AbstractMonitor(String name) { + this.name = name; + } + + /** + * The invariant. The default implementation always returns true. This method + * should be overridden if at all possible with the strongest economically + * evaluable invariant. + */ + protected boolean invariant() { + return true; + } + + /** + * Enter the monitor. Any thread calling this method is delayed until the + * monitor is unoccupied. Upon returning from this method, the monitor is + * considered occupied. A thread must not attempt to enter a Monitor it is + * already in. + */ + protected void enter() { + notifyCallEnter(); + entrance.acquire(); + // The following assertion should never trip! + Assertion.check(occupant == null, "2 threads in one monitor"); + occupant = Thread.currentThread(); + notifyReturnFromEnter(); + Assertion.check(invariant(), "Invariant of monitor " + getName()); + } + + /** + * Leave the monitor. After returning from this method, the thread no longer + * occupies the monitor. Only a thread that is in the monitor may leave it. + * + * @throws AssertionError + * if the thread that leaves is not the occupant. + */ + protected void leave() { + notifyLeaveMonitor(); + leaveWithoutATrace(); + } + + /** + * Leave the monitor. After returning from this method, the thread no longer + * occupies the monitor. Only a thread that is in the monitor may leave it. + * + * @throws AssertionError + * if the thread that leaves is not the occupant. + */ + protected T leave(T result) { + leave(); + return result; + } + + void leaveWithoutATrace() { + Assertion.check(invariant(), "Invariant of monitor " + getName()); + Assertion.check(occupant == Thread.currentThread(), "Thread is not occupant"); + occupant = null; + entrance.release(); + } + + /** + * Run the runnable inside the monitor. Any thread calling this method will be + * delayed until the monitor is empty. The "run" method of its argument is then + * executed within the protection of the monitor. When the run method returns, + * if the thread still occupies the monitor, it leaves the monitor. + * + * @param runnable + * A Runnable object. + */ + protected void doWithin(Runnable runnable) { + enter(); + try { + runnable.run(); + } finally { + if (occupant == Thread.currentThread()) { + leave(); + } + } + } + + /** + * Run the runnable inside the monitor. Any thread calling this method will be + * delayed until the monitor is empty. The "run" method of its argument is then + * executed within the protection of the monitor. When the run method returns, + * if the thread still occupies the monitor, it leaves the monitor. Thus the + * signalAndLeave method may be called within the run method. + * + * @param runnable + * A RunnableWithResult object. + * @return The value computed by the run method of the runnable. + */ + protected T doWithin(RunnableWithResult runnable) { + enter(); + try { + return runnable.run(); + } finally { + if (occupant == Thread.currentThread()) { + leave(); + } + } + } + + /** + * Create a condition queue associated with a checked Assertion. The Assertion + * will be checked prior to an signal of the condition. + */ + protected Condition makeCondition(Assertion prop) { + return makeCondition(null, prop); + } + + /** + * Create a condition queue with no associated checked Assertion. + */ + protected Condition makeCondition() { + return makeCondition(null, TrueAssertion.singleton); + } + + /** + * Create a condition queue associated with a checked Assertion. The Assertion + * will be checked prior to an signal of the condition. + */ + protected Condition makeCondition(String name, Assertion prop) { + return new Condition(name, this, prop); + } + + /** + * Create a condition queue with no associated checked Assertion. + */ + protected Condition makeCondition(String name) { + return makeCondition(name, TrueAssertion.singleton); + } + + /** Register a listener. */ + public void addListener(MonitorListener newListener) { + listOfListeners.add(newListener); + } + + private void notifyCallEnter() { + for (MonitorListener listener : listOfListeners) { + listener.callEnterMonitor(this); + } + } + + private void notifyReturnFromEnter() { + for (MonitorListener listener : listOfListeners) { + listener.returnFromEnterMonitor(this); + } + } + + private void notifyLeaveMonitor() { + for (MonitorListener listener : listOfListeners) { + listener.leaveMonitor(this); + } + } + + void notifyCallAwait(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.callAwait(condition, this); + } + } + + void notifyReturnFromAwait(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.returnFromAwait(condition, this); + } + } + + void notifySignallerAwakesAwaitingThread(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.signallerAwakesAwaitingThread(condition, this); + } + } + + void notifySignallerLeavesTemporarily(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.signallerLeavesTemporarily(condition, this); + } + } + + void notifySignallerReenters(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.signallerReenters(condition, this); + } + } + + void notifySignallerLeavesMonitor(Condition condition) { + for (MonitorListener listener : listOfListeners) { + listener.signallerLeavesMonitor(condition, this); + } + } +} diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/Assertion.java b/monitor-object/src/main/java/com/iluwatar/monitor/Assertion.java new file mode 100644 index 000000000..b77586c10 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/Assertion.java @@ -0,0 +1 @@ +package com.iluwatar.monitor; /** * Assertions that may be checked from time to time. */ public abstract class Assertion { private static final String DEFAULTMESSAGE = "Assertion Failure"; protected String message = DEFAULTMESSAGE; /** This method says whether the assertion is true. */ public abstract boolean isTrue(); /** Throw an AssertionError if the assertion is not true. */ public void check() { check(isTrue(), message); } /** Throw an AssertionError if the parameter is not true. */ public static void check(boolean b) { check(b, DEFAULTMESSAGE); } /** Throw an AssertionError if the boolean parameter is not true. */ public static void check(boolean b, String message) { if (!b) { throw new AssertionError(message); } } } \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/Condition.java b/monitor-object/src/main/java/com/iluwatar/monitor/Condition.java new file mode 100644 index 000000000..be104770f --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/Condition.java @@ -0,0 +1 @@ +package com.iluwatar.monitor; /** * A condition queue. * Uses the signal and wait discipline (SW) or signal and leave (SL). * Each Condition object is associated with a single {@link Assertion} object * and a single AbstractMonitor object. To construct a condition * use the makeCondition methods from * {@link AbstractMonitor} or {@link Monitor} * Threads can wait for the assertion represented by the Assertion object to * become true by calling method await(). Threads can indicate that * the assertion has become true by calling either method signal() * (SW) or signalAndLeave() (SL). All these methods check the * assertion and the monitor's invariant as appropriate. * Threads which wait on a Condition may supply a priority. In the absence of * priority, waiting is fair -- in fact first-in last-out (FIFO). * Each of the await(), signal(), and * signalAndLeave() methods have corresponding conditional * versions, which first check the assertion before awaiting or signalling. * These are: conditionalAwait(), conditionalSignal(), * and conditionalSignalAndLeave(). * Conditions also support a {@link #count} accessor to determine the number of * threads waiting on the condition. * Condition objects are intended to be used only by the thread which occupies * the monitor which created them. */ public class Condition { private final AbstractMonitor homeMonitor; private final Assertion assertion; private final Semaphore queue; private volatile int count; private final String name; Condition(String name, AbstractMonitor homeMonitor, Assertion assertion) { this.name = name; this.homeMonitor = homeMonitor; this.assertion = assertion; this.queue = new Semaphore(0); this.count = 0; } public String getName() { return name; } /** * Just like await, but with a priority. Threads awaiting with a lesser priority * value are re-admitted to the monitor in preference to threads awaiting with a * greater priority value. When priority values are the same, the order is FIFO. * * @param priority * Lower value means more urgent. * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if the invariant is not true to start * @throws AssertionError * if the assertion is not true on return * @see #await() */ public void await(int priority) { homeMonitor.notifyCallAwait(this); Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); count += 1; Assertion.check(homeMonitor.invariant(), "Invariant of monitor " + homeMonitor.getName()); homeMonitor.occupant = null; homeMonitor.entrance.release(); queue.acquire(priority); count -= 1; homeMonitor.occupant = Thread.currentThread(); // It's not clear that the following check is needed anymore, // as there is now a check made on the signal. assertion.check(); homeMonitor.notifyReturnFromAwait(this); } /** * Wait until a condition is signalled. The thread waits outside the monitor * until the condition is signalled. * Precondition: Increasing the count by 1 must make the invariant true. This * thread is in the monitor. * Postcondition: The assertion associated with this condition queue. This * thread is in the monitor. * Note: threads are queued in a FIFO manner unless a priority is used; * cond.await() is equivalent to cond.await( Integer.MAX_VALUE * ). * * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if the invariant is not true to start * @throws AssertionError * if the assertion is not true on return */ public void await() { await(Integer.MAX_VALUE); } /** * Wait only if the condition is not already true. * * @throws AssertionError * if neither the invariant nor the assertion associated with this * object is true * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if the assertion is not true on return * @see #await() */ public void conditionalAwait() { conditionalAwait(Integer.MAX_VALUE); } /** * Just like conditionalAwait, but with a priority. * * @param priority * Lower value means more urgent. * @throws AssertionError * if neither the invariant nor the assertion associated with this * object is true * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if the assertion is not true on return * @see #conditionalAwait() * @see #await( int priority ) * */ public void conditionalAwait(int priority) { if (!assertion.isTrue()) { await(priority); } } /** * Signal this condition if there is a waiting thread. * Allows one thread that was waiting on the condition to reenter the monitor. * Consequently the signalling thread waits outside. The signalling thread is * allowed back into the monitor, once the monitor is again unoccupied. Threads * which have signalled wait with a higher than normal priority and thus are * allowed in ahead of other threads that are waiting to enter the monitor * (e.g., those waiting in {@link AbstractMonitor#enter()}). * If there is no waiting thread, then this is a no-op, but the invariant is * still checked, as it is a postcondition. * Preconditions: *
    *
  • If isEmpty(), the monitor's invariant must be true.
  • *
  • If not isEmpty(), then decreasing count() by 1 must make the proposition * associated with this condition true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: *
    *
  • The monitor's invariant. *
  • This thread is in the monitor. *
* * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if there is a waiting thread and the assertion is false (after * decreasing the count by 1). * @throws AssertionError * if invariant is false on return. * */ public void signal() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); if (count > 0) { try { count -= 1; assertion.check(); } finally { count += 1; } homeMonitor.notifySignallerLeavesTemporarily(this); homeMonitor.notifySignallerAwakesAwaitingThread(this); homeMonitor.occupant = null; queue.release(); homeMonitor.entrance.acquire(0); // Priority 0 puts the signaller ahead of others. homeMonitor.occupant = Thread.currentThread(); homeMonitor.notifySignallerReenters(this); } Assertion.check(homeMonitor.invariant(), "Invariant of monitor " + homeMonitor.getName()); } /** * Allows one thread which was waiting on the condition to reenter the monitor. * This thread (the one calling signalAndLeave) leaves the monitor immediately. * Preconditions: *
    *
  • If isEmpty(), the monitor's invariant must be true.
  • *
  • If not isEmpty(), then decreasing count() by 1 must make the proposition * associated with this condition true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: This thread is not in the monitor. * * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if there is a waiting thread and the assertion is false (after * decreasing the count by 1). * @throws AssertionError * if there is no waiting thread and the invariant is false. * @see #signal() */ public void signalAndLeave() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); if (count > 0) { try { count -= 1; assertion.check(); } finally { count += 1; } homeMonitor.notifySignallerAwakesAwaitingThread(this); homeMonitor.occupant = null; queue.release(); } else { Assertion.check(homeMonitor.invariant(), "Invariant of monitor " + homeMonitor.getName()); homeMonitor.occupant = null; homeMonitor.entrance.release(); } homeMonitor.notifySignallerLeavesMonitor(this); } /** * Signal if there is a waiting thread, then leave the monitor. Allows one * thread which was waiting on the condition to reenter the monitor. This thread * (the one calling signalAndLeave) leaves the monitor immediately. * Preconditions: *
    *
  • If isEmpty(), the monitor's invariant must be true.
  • *
  • If not isEmpty(), then decreasing count() by 1 must make the proposition * associated with this condition true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: This thread is not in the monitor. * * @param result * A value to return. * @return The value of the result parameter. * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if there is a waiting thread and the assertion is false (after * decreasing the count by 1). * @throws AssertionError * there is no waiting thread and the invariant is false. */ public T signalAndLeave(T result) { signalAndLeave(); return result; } /** * Signal this condition if its assertion is true and there is a waiting thread. * More precisely the condition is only signalled if its assertion would be true * after the count is decreased by 1 and there is a waiting tread. * In such a case, the signalling thread waits outside. The signalling thread is * allowed back into the monitor, once the monitor is again unoccupied. Threads * which have signalled wait with a higher than normal priority and thus are * allowed in ahead of other threads that are waiting to enter the monitor * (e.g., those waiting in {@link AbstractMonitor#enter()}). * If the there are no awaiting threads, or the condition's assertion would not * be true after the count were decreased by one, this method is essentially a * no-op, although the invariant is still checked in such a case. * Preconditions: *
    *
  • If isEmpty(), the monitor's invariant must be true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: *
    *
  • The monitor's invariant. *
  • This thread is in the monitor. *
* * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * if invariant is false on return. * @see #signal() */ public void conditionalSignal() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); if (count > 0) { boolean wouldBeTrue; count -= 1; wouldBeTrue = assertion.isTrue(); count += 1; if (wouldBeTrue) { homeMonitor.notifySignallerAwakesAwaitingThread(this); homeMonitor.notifySignallerLeavesTemporarily(this); homeMonitor.occupant = null; queue.release(); homeMonitor.entrance.acquire(); homeMonitor.occupant = Thread.currentThread(); homeMonitor.notifySignallerReenters(this); } } Assertion.check(homeMonitor.invariant(), "Invariant of monitor " + homeMonitor.getName()); } /** * Signal this condition if its assertion is true and there is a waiting thread; * leave regardless. * More precisely the condition is only signalled if the assertion would be true * after the count is decreased by 1 and there is a waiting thread. * This thread (the one calling signalAndLeave) leaves the monitor immediately. * Preconditions: *
    *
  • If isEmpty() or the assertion would be false after decreasing the count * by 1, the monitor's invariant must be true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: *
    *
  • This thread is not in the monitor. *
* * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * there is no waiting thread and the invariant is false. * @throws AssertionError * if there is a waiting thread and the assertion is false (after * decreasing the count by 1) and the invariant is false. * @see #signalAndLeave() * @see #conditionalSignal() * */ public void conditionalSignalAndLeave() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); if (count > 0) { boolean wouldBeTrue; count -= 1; wouldBeTrue = assertion.isTrue(); count += 1; if (wouldBeTrue) { homeMonitor.notifySignallerAwakesAwaitingThread(this); homeMonitor.notifySignallerLeavesMonitor(this); homeMonitor.occupant = null; queue.release(); } else { homeMonitor.notifySignallerLeavesMonitor(this); homeMonitor.leaveWithoutATrace(); } } else { homeMonitor.notifySignallerLeavesMonitor(this); homeMonitor.leaveWithoutATrace(); } } /** * Signal this condition if its assertion is true and there is a waiting thread. * Leave regardless. More precisely the condition is only signalled if the * assertion would be true after the count is decreased by 1. * This thread (the one calling signalAndLeave) leaves the monitor immediately. * Preconditions: *
    *
  • If isEmpty() or the assertion would be false after decreasing the count * by 1, the monitor's invariant must be true.
  • *
  • This thread is in the monitor.
  • *
* Postcondition: *
    *
  • This thread is not in the monitor. *
* * @param result * A value to be returned. * @return The value of the result parameter. * @throws AssertionError * if the current thread is not the occupant. * @throws AssertionError * there is no waiting thread and the invariant is false. * @throws AssertionError * if there is a waiting thread and the assertion is false (after * decreasing the count by 1) and the invariant is false. * @see #conditionalSignalAndLeave() * */ public T conditionalSignalAndLeave(T result) { conditionalSignalAndLeave(); return result; } /** * Test if any thread is waiting on this condition. * * @return count() == 0 . * @throws AssertionError * if the current thread is not the occupant. */ public boolean isEmpty() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); return count == 0; } /** * How many threads are waiting on this condition. * * @return the number of Threads waiting on this condition. * @throws AssertionError * if the current thread is not the occupant. */ public int count() { Assertion.check(homeMonitor.occupant == Thread.currentThread(), "Thread is not occupant"); return count; } } \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/Monitor.java b/monitor-object/src/main/java/com/iluwatar/monitor/Monitor.java new file mode 100644 index 000000000..bf2ed5915 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/Monitor.java @@ -0,0 +1,77 @@ +package com.iluwatar.monitor; + +/** + * A final class for Monitors. + */ + +public final class Monitor extends AbstractMonitor { + + private Assertion invariant; + + public Monitor() { + this(TrueAssertion.singleton); + } + + public Monitor(Assertion invariant) { + this.invariant = invariant; + } + + public Monitor(String name) { + this(name, TrueAssertion.singleton); + } + + public Monitor(String name, Assertion invariant) { + super(name); + this.invariant = invariant; + } + + @Override + public boolean invariant() { + return invariant.isTrue(); + } + + @Override + public void enter() { + super.enter(); + } + + @Override + public void leave() { + super.leave(); + } + + @Override + public T leave(T result) { + return super.leave(result); + } + + @Override + public void doWithin(Runnable runnable) { + super.doWithin(runnable); + } + + @Override + public T doWithin(RunnableWithResult runnable) { + return super.doWithin(runnable); + } + + @Override + public Condition makeCondition() { + return super.makeCondition(); + } + + @Override + public Condition makeCondition(Assertion assertion) { + return super.makeCondition(assertion); + } + + @Override + public Condition makeCondition(String name) { + return super.makeCondition(name); + } + + @Override + public Condition makeCondition(String name, Assertion assertion) { + return super.makeCondition(name, assertion); + } +} \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/MonitorListener.java b/monitor-object/src/main/java/com/iluwatar/monitor/MonitorListener.java new file mode 100644 index 000000000..120e9bf35 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/MonitorListener.java @@ -0,0 +1,25 @@ +package com.iluwatar.monitor; + +public interface MonitorListener { + + void nameThisThread(String name); + + void callEnterMonitor(AbstractMonitor monitor); + + void returnFromEnterMonitor(AbstractMonitor monitor); + + void leaveMonitor(AbstractMonitor monitor); + + void callAwait(Condition condition, AbstractMonitor monitor); + + void returnFromAwait(Condition condition, AbstractMonitor monitor); + + void signallerAwakesAwaitingThread(Condition condition, AbstractMonitor monitor); + + void signallerLeavesTemporarily(Condition condition, AbstractMonitor monitor); + + void signallerReenters(Condition condition, AbstractMonitor monitor); + + void signallerLeavesMonitor(Condition condition, AbstractMonitor monitor); + +} \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/RunnableWithResult.java b/monitor-object/src/main/java/com/iluwatar/monitor/RunnableWithResult.java new file mode 100644 index 000000000..48b4ea06d --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/RunnableWithResult.java @@ -0,0 +1,5 @@ +package com.iluwatar.monitor; + +public interface RunnableWithResult { + public T run(); +} diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/Semaphore.java b/monitor-object/src/main/java/com/iluwatar/monitor/Semaphore.java new file mode 100644 index 000000000..fe45eac11 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/Semaphore.java @@ -0,0 +1 @@ +package com.iluwatar.monitor; import java.util.LinkedList; import java.util.ListIterator; /** * A FIFO semaphore. */ public class Semaphore { // Each queue element is a a single use semaphore class QueueElement { final int priority; volatile boolean enabled = false; QueueElement(int priority) { this.priority = priority; } synchronized void acquire() { while (!enabled) { try { wait(); } catch (InterruptedException e) { throw new RuntimeException("Unexpected interruption of " + "thread in monitor.Semaphore.acquire"); } } } synchronized void release() { enabled = true; notify(); } } volatile int s1; final LinkedList queue = new LinkedList(); // Invariant. All elements on the queue are in an unenabled state. /** Initialize the semaphore to a value greater or equal to 0. */ public Semaphore(int initialvalue) { Assertion.check(initialvalue >= 0); this.s1 = initialvalue; } /** * The P operation. If two threads are blocked at the same time, they will be * served in FIFO order. sem.acquire() is equivalent to acquire( * Integer.MAX_VALUE ). */ public void acquire() { acquire(Integer.MAX_VALUE); } /** * The P operation with a priority. * * @param priority * The larger the integer, the less urgent the priority. If two * thread are waiting with equal priority, they will complete acquire * in FIFO order. */ public void acquire(int priority) { QueueElement mine; synchronized (this) { if (s1 > 0) { --s1; return; } mine = new QueueElement(priority); if (priority == Integer.MAX_VALUE) { queue.add(mine); } else { ListIterator it = queue.listIterator(0); int i = 0; while (it.hasNext()) { QueueElement elem = it.next(); if (elem.priority > priority) { break; } ++i; } queue.add(i, mine); } } mine.acquire(); } /** The V operation. */ public synchronized void release() { QueueElement first = queue.poll(); if (first != null) { first.release(); } else { ++s1; } } } \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/TrueAssertion.java b/monitor-object/src/main/java/com/iluwatar/monitor/TrueAssertion.java new file mode 100644 index 000000000..82d6bb0e5 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/TrueAssertion.java @@ -0,0 +1 @@ +package com.iluwatar.monitor; /** * An assertion that is always true. */ public class TrueAssertion extends Assertion { public boolean isTrue() { return true; } public static final TrueAssertion singleton = new TrueAssertion(); } \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/examples/Queue.java b/monitor-object/src/main/java/com/iluwatar/monitor/examples/Queue.java new file mode 100644 index 000000000..18ecba2ff --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/examples/Queue.java @@ -0,0 +1,66 @@ +package com.iluwatar.monitor.examples; + +import com.iluwatar.monitor.AbstractMonitor; +import com.iluwatar.monitor.Condition; + +/** + * FIFO Queue implementation using {@link AbstractMonitor}. + */ +public class Queue extends AbstractMonitor { + + private final int capacity = 10; + + private final Object[] queue = new Object[capacity]; + + private volatile int count = 0; + + private volatile int front = 0; + + /** Awaiting ensures: count < capacity. */ + private final Condition notFull = makeCondition(); + + /** Awaiting ensures: count > 0. */ + private final Condition notEmpty = makeCondition(); + + /** + * Method to pop the front element from queue. + * @return the top most element + */ + public Object fetch() { + enter(); + if (!(count > 0)) { + notEmpty.await(); + assert count > 0; + } + count--; + front = (front + 1) % capacity; + assert count < capacity; + notFull.signal(); + leave(); + Object value = queue[front]; + return value; + } + + /** + * Method to push an element in queue. + * @param value the element to be pushed + */ + public void deposit(Object value) { + enter(); + if (!(count < capacity)) { + notFull.await(); + assert count < capacity; + } + queue[(front + count) % capacity] = value; + count++; + assert count > 0; + notEmpty.signal(); + leave(); + } + + @Override + protected boolean invariant() { + return 0 <= count && count <= capacity && 0 <= front && front < capacity; + } + +} \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteInterface.java b/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteInterface.java new file mode 100644 index 000000000..54111ea54 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteInterface.java @@ -0,0 +1,9 @@ +package com.iluwatar.monitor.examples; + +/** + * Monitor Example. + */ + +public interface VoteInterface { + public boolean castVoteAndWaitForResult(boolean vote); +} \ No newline at end of file diff --git a/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteMonitor.java b/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteMonitor.java new file mode 100644 index 000000000..a58919e60 --- /dev/null +++ b/monitor-object/src/main/java/com/iluwatar/monitor/examples/VoteMonitor.java @@ -0,0 +1,63 @@ +package com.iluwatar.monitor.examples; + +import com.iluwatar.monitor.AbstractMonitor; +import com.iluwatar.monitor.Assertion; +import com.iluwatar.monitor.Condition; +import com.iluwatar.monitor.RunnableWithResult; + +/** + * Monitor Example. + */ + +public class VoteMonitor extends AbstractMonitor implements VoteInterface { + + private final int totalVotes; + private int votesFor = 0; + private int votesAgainst = 0; + + private Condition electionDone = makeCondition(new Assertion() { + @Override + public boolean isTrue() { + return votesFor + votesAgainst == totalVotes; + } + }); + + public VoteMonitor(int n) { + assert n > 0; + this.totalVotes = n; + } + + @Override + protected boolean invariant() { + return 0 <= votesFor && 0 <= votesAgainst && votesFor + votesAgainst < totalVotes; + } + + /** + * Method to cast a vote and wait for the result. + */ + public boolean castVoteAndWaitForResult(final boolean vote) { + return doWithin(new RunnableWithResult() { + public Boolean run() { + if (vote) { + votesFor++; + } else { + votesAgainst++; + } + + electionDone.conditionalAwait(); + + // Assert: votesFor+votesAgainst == N + boolean result = votesFor > votesAgainst; + + if (!electionDone.isEmpty()) { + electionDone.signalAndLeave(); + } else { + votesFor = votesAgainst = 0; + } + // At this point the thread could be occupying + // the monitor, or not! + return result; + } + }); + } +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/AssertionTest.java b/monitor-object/src/test/java/com/iluwatar/monitor/AssertionTest.java new file mode 100644 index 000000000..9f6a48372 --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/AssertionTest.java @@ -0,0 +1,44 @@ +package com.iluwatar.monitor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Test Cases for Assertion. + */ +public class AssertionTest { + int varX = 0; + int varY = 0; + + @Test + public void testAssertionTrue() { + Assertion a = new MyAssertion(); + assertEquals(true, a.isTrue()); + } + + @Test + public void testAssertionFalse() { + Assertion a = new MyAssertion(); + a.check(); + varX = 1; + assertEquals(false, a.isTrue()); + } + + @Test + public void testAssertionError() { + assertThrows(NullPointerException.class, () -> { + Assertion a = new MyAssertion(); + a.check(); + varX = 1; + a.check(); + }); + } + + class MyAssertion extends Assertion { + public boolean isTrue() { + return varX == varY; + } + } +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/ThreadSignalTest.java b/monitor-object/src/test/java/com/iluwatar/monitor/ThreadSignalTest.java new file mode 100644 index 000000000..553f88ac5 --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/ThreadSignalTest.java @@ -0,0 +1,43 @@ +package com.iluwatar.monitor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.iluwatar.monitor.examples.VoteInterface; +import com.iluwatar.monitor.examples.VoteMonitor; + +/** + * Test Case for Thread Signaling. + */ +public class ThreadSignalTest { + + private Voter v0; + private Voter v1; + private Voter v2; + + /** + * Setup method for Test Case. + */ + @BeforeAll + public void setUp() { + VoteInterface vm = new VoteMonitor(3); + v0 = new Voter0(vm); + v1 = new Voter1(vm); + v2 = new Voter2(vm); + } + + @Test + public void testVotingResult() throws InterruptedException { + v0.start(); + v1.start(); + v2.start(); + v0.join(); + v1.join(); + v2.join(); + assertEquals("Passed", v0.getTestResult()); + assertEquals("Passed", v1.getTestResult()); + assertEquals("Passed", v2.getTestResult()); + } +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/Voter.java b/monitor-object/src/test/java/com/iluwatar/monitor/Voter.java new file mode 100644 index 000000000..501405a00 --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/Voter.java @@ -0,0 +1,40 @@ +package com.iluwatar.monitor; + +import com.iluwatar.monitor.examples.VoteInterface; + +abstract class Voter extends Thread { + + private VoteInterface vm; + + private String testResult; + + public String getTestResult() { + return testResult; + } + + public void setTestResult(String testResult) { + this.testResult = testResult; + } + + Voter(VoteInterface vm) { + this.vm = vm; + } + + public void run() { + for (int i = 0; i < 100; ++i) { + boolean vote = makeVote(i); + boolean consensus = vm.castVoteAndWaitForResult(vote); + boolean expected = i % 6 == 1 || i % 6 == 2 || i % 6 == 5; + if (expected != consensus) { + System.out.println("Failed"); + setTestResult("Failed"); + System.exit(1); + } + setTestResult("Passed"); + System.out.println(i + ": " + consensus); + } + System.out.println("Done"); + } + + abstract boolean makeVote(int i); +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/Voter0.java b/monitor-object/src/test/java/com/iluwatar/monitor/Voter0.java new file mode 100644 index 000000000..60d2e312b --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/Voter0.java @@ -0,0 +1,13 @@ +package com.iluwatar.monitor; + +import com.iluwatar.monitor.examples.VoteInterface; + +class Voter0 extends Voter { + Voter0(VoteInterface vm) { + super(vm); + } + + boolean makeVote(int i) { + return i % 2 == 1; + } +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/Voter1.java b/monitor-object/src/test/java/com/iluwatar/monitor/Voter1.java new file mode 100644 index 000000000..291e13745 --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/Voter1.java @@ -0,0 +1,13 @@ +package com.iluwatar.monitor; + +import com.iluwatar.monitor.examples.VoteInterface; + +class Voter1 extends Voter { + Voter1(VoteInterface vm) { + super(vm); + } + + boolean makeVote(int i) { + return i % 3 == 2; + } +} \ No newline at end of file diff --git a/monitor-object/src/test/java/com/iluwatar/monitor/Voter2.java b/monitor-object/src/test/java/com/iluwatar/monitor/Voter2.java new file mode 100644 index 000000000..c672b693e --- /dev/null +++ b/monitor-object/src/test/java/com/iluwatar/monitor/Voter2.java @@ -0,0 +1,13 @@ +package com.iluwatar.monitor; + +import com.iluwatar.monitor.examples.VoteInterface; + +class Voter2 extends Voter { + Voter2(VoteInterface vm) { + super(vm); + } + + boolean makeVote(int i) { + return i % 3 != 0; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 741367b30..eb6575ea0 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,7 @@ trampoline serverless component-object + monitor-object