Related
I already know how Event Dispatch thread works. If there be short and long events in Event Dispatch thread like below, the application can't be responsive.
For the Sake of responsiveness in Swing, Event Dispatch thread should only be used for short events. while long events should be executed on SwingWorkers.
Imagine that there is a lot of short events.
The events should be executed in Event Dispatch thread and you have a special event which you want to be executed before other events existing in Event Dispatch Thread queue. But, events will be enqueued to the end of the queue by default and even InvokeLater do the same.
So, is there any solution to enqueue an event to the beginning of the Event Dispatch Thread?
Although replacing the EventQueue is a right approach, it's not really necessary since built-in EventQueue already supports prioritizing. Only thing is it only supports it for inner API use so we only need to understand how that works;
//from EventQueue.java...
private static final int LOW_PRIORITY = 0;
private static final int NORM_PRIORITY = 1;
private static final int HIGH_PRIORITY = 2;
private static final int ULTIMATE_PRIORITY = 3;
private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;
/*
* We maintain one Queue for each priority that the EventQueue supports.
* That is, the EventQueue object is actually implemented as
* NUM_PRIORITIES queues and all Events on a particular internal Queue
* have identical priority. Events are pulled off the EventQueue starting
* with the Queue of highest priority. We progress in decreasing order
* across all Queues.
*/
private Queue[] queues = new Queue[NUM_PRIORITIES];
//...skipped some parts...
/**
* Causes <code>runnable</code> to have its <code>run</code>
* method called in the {#link #isDispatchThread dispatch thread} of
* {#link Toolkit#getSystemEventQueue the system EventQueue}.
* This will happen after all pending events are processed.
*
* #param runnable the <code>Runnable</code> whose <code>run</code>
* method should be executed
* asynchronously in the
* {#link #isDispatchThread event dispatch thread}
* of {#link Toolkit#getSystemEventQueue the system EventQueue}
* #see #invokeAndWait
* #see Toolkit#getSystemEventQueue
* #see #isDispatchThread
* #since 1.2
*/
public static void invokeLater(Runnable runnable) {
Toolkit.getEventQueue().postEvent(
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}
/**
* Posts a 1.1-style event to the <code>EventQueue</code>.
* If there is an existing event on the queue with the same ID
* and event source, the source <code>Component</code>'s
* <code>coalesceEvents</code> method will be called.
*
* #param theEvent an instance of <code>java.awt.AWTEvent</code>,
* or a subclass of it
* #throws NullPointerException if <code>theEvent</code> is <code>null</code>
*/
public void postEvent(AWTEvent theEvent) {
SunToolkit.flushPendingEvents(appContext);
postEventPrivate(theEvent);
}
/**
* Posts a 1.1-style event to the <code>EventQueue</code>.
* If there is an existing event on the queue with the same ID
* and event source, the source <code>Component</code>'s
* <code>coalesceEvents</code> method will be called.
*
* #param theEvent an instance of <code>java.awt.AWTEvent</code>,
* or a subclass of it
*/
private final void postEventPrivate(AWTEvent theEvent) {
theEvent.isPosted = true;
pushPopLock.lock();
try {
if (nextQueue != null) {
// Forward the event to the top of EventQueue stack
nextQueue.postEventPrivate(theEvent);
return;
}
if (dispatchThread == null) {
if (theEvent.getSource() == AWTAutoShutdown.getInstance()) {
return;
} else {
initDispatchThread();
}
}
postEvent(theEvent, getPriority(theEvent));
} finally {
pushPopLock.unlock();
}
}
private static int getPriority(AWTEvent theEvent) {
if (theEvent instanceof PeerEvent) {
PeerEvent peerEvent = (PeerEvent)theEvent;
if ((peerEvent.getFlags() & PeerEvent.ULTIMATE_PRIORITY_EVENT) != 0) {
return ULTIMATE_PRIORITY;
}
if ((peerEvent.getFlags() & PeerEvent.PRIORITY_EVENT) != 0) {
return HIGH_PRIORITY;
}
if ((peerEvent.getFlags() & PeerEvent.LOW_PRIORITY_EVENT) != 0) {
return LOW_PRIORITY;
}
}
int id = theEvent.getID();
if ((id >= PaintEvent.PAINT_FIRST) && (id <= PaintEvent.PAINT_LAST)) {
return LOW_PRIORITY;
}
return NORM_PRIORITY;
}
/**
* Posts the event to the internal Queue of specified priority,
* coalescing as appropriate.
*
* #param theEvent an instance of <code>java.awt.AWTEvent</code>,
* or a subclass of it
* #param priority the desired priority of the event
*/
private void postEvent(AWTEvent theEvent, int priority) {
if (coalesceEvent(theEvent, priority)) {
return;
}
EventQueueItem newItem = new EventQueueItem(theEvent);
cacheEQItem(newItem);
boolean notifyID = (theEvent.getID() == this.waitForID);
if (queues[priority].head == null) {
boolean shouldNotify = noEvents();
queues[priority].head = queues[priority].tail = newItem;
if (shouldNotify) {
if (theEvent.getSource() != AWTAutoShutdown.getInstance()) {
AWTAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
}
pushPopCond.signalAll();
} else if (notifyID) {
pushPopCond.signalAll();
}
} else {
// The event was not coalesced or has non-Component source.
// Insert it at the end of the appropriate Queue.
queues[priority].tail.next = newItem;
queues[priority].tail = newItem;
if (notifyID) {
pushPopCond.signalAll();
}
}
}
As you can see EventQueue have 4 different queues as LOW, NORM, HIGH and ULTIMATE, SwingUtilities.invokeLater(Runnable) or EventQueue.invokeLater(Runnable) wraps your Runnable into an InvocationEvent and calls postEvent(AWTEvent) method. This method does some syncronizing between threads and calls postEvent(AWTEvent, int) like this postEvent(theEvent, getPriority(theEvent)); Now the interesting part is how getPriority(AWTEvent) works, basicly it gives normal priority to the every event except some PaintEvents and PeerEvents.
So what you need to do is wrap your Runnable into a PeerEvent with ULTIMATE_PRIORTY instead of a InvocationEvent like this;
Toolkit.getDefaultToolkit().getSystemEventQueue()
.postEvent(new PeerEvent(Toolkit.getDefaultToolkit(), () -> {
//execute your high priority task here!
System.out.println("I'm ultimate prioritized in EventQueue!");
}, PeerEvent.ULTIMATE_PRIORITY_EVENT));
You can check the full source code of EventQueue and PeerEvent .
You can create and use your own Event Queue that inserts new events in the way you want it. See the code snippet below how to setup a custom Event Queue:
public class QueueTest {
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
eventQueue.push(new MyEventQueue());
EventQueue.invokeAndWait(new Runnable() {
public void run() {
System.out.println("Run");
}
});
}
private static class MyEventQueue extends EventQueue {
public void postEvent(AWTEvent theEvent) {
System.out.println("Event Posted");
super.postEvent(theEvent);
}
}
}
Your custom Event Queue could then post specific events that you want to be prepended to the queue with the highest priority. This might not ensure that it is the next event to be processed, but would probably fit best into the existing design.
My initial thought was
I do not think we can control the tasks which needs to be picked up by Event Dispatch Thread, but in certain ways we can try to set the priority like below
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
// The task which need immediate attention.
}});
Again there is no guarantee that this would be picked up for immediate execution by EDT.
But the above code is wrong. By the time run gets called it is already executing the tasks. Thanks for the comments Onur.
So the code below should help.
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
Runnable runnable = new Runnable() {
#Override
public void run() {
//My high priority task
}
};
PeerEvent event = new PeerEvent(this, runnable, PeerEvent.ULTIMATE_PRIORITY_EVENT);
queue.postEvent(event);
But there is one point we need to notice.
private static final int NUM_PRIORITIES = ULTIMATE_PRIORITY + 1;
/*
* We maintain one Queue for each priority that the EventQueue supports.
* That is, the EventQueue object is actually implemented as
* NUM_PRIORITIES queues and all Events on a particular internal Queue
* have identical priority. Events are pulled off the EventQueue starting
* with the Queue of highest priority. We progress in decreasing order
* across all Queues.
*/
private Queue[] queues = new Queue[NUM_PRIORITIES];
public EventQueue() {
for (int i = 0; i < NUM_PRIORITIES; i++) {
queues[i] = new Queue();
}
....
}
So if we are setting too many ULTIMATE_PRIORITY tasks, there is no guarantee that the latest task would be executed immediately.
I am learning multithreading in Java. Here I am stuck with the getId() method. How does it return "8"? Then, I am also having a doubt with "Thread-0" of t1 and "Thread-1" of t2, are these the initial values of the getName() method?
class TestJoinMethod3 extends Thread{
public void run(){
System.out.println("running...");
}
public static void main(String args[]){
TestJoinMethod3 t1=new TestJoinMethod3();
TestJoinMethod3 t2=new TestJoinMethod3();
System.out.println("Name of t1:"+t1.getName());
System.out.println("Name of t2:"+t2.getName());
System.out.println("id of t1:"+t1.getId());
t1.start();
t2.start();
t1.setName("Sonoo Jaiswal");
System.out.println("After changing name of t1:"+t1.getName());
}
}
OUTPUT
Name of t1:Thread-0
Name of t2:Thread-1
id of t1:8
running...
After changling name of t1:Sonoo Jaiswal
running...
i am stuck with getId() method
As the Javadoc for getId states
/**
* Returns the identifier of this Thread. The thread ID is a positive
* <tt>long</tt> number generated when this thread was created.
* The thread ID is unique and remains unchanged during its lifetime.
* When a thread is terminated, this thread ID may be reused.
*
* #return this thread's ID.
* #since 1.5
*/
public long getId() {
return tid;
}
and
tid = nextThreadID();
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
The JVM has internal threads so the first thread you create might be the 9th thread. Note: main is a thread.
Then i am also having doubt with "Thread-0" of t1 and "Thread-1" of t2, is this the initial value of getName() method?
Similarly the default name for a thread is generated when you create it
/**
* Allocates a new {#code Thread} object. This constructor has the same
* effect as {#linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {#code (null, target, gname)}, where {#code gname} is a newly generated
* name. Automatically generated names are of the form
* {#code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*
* #param target
* the object whose {#code run} method is invoked when this thread
* is started. If {#code null}, this classes {#code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
It gets generated in the method nextId, which just counts up.
Some snippets from the source code.
public long getId() {
return tid;
}
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
// lots of other stuff
tid = nextThreadID();
}
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
Other constructors do similar calls to init
After spending lots of time with threadpool concepts and by reading different codes on numbers of blogs and posting questions on Stackoverflow.com, now I got clear image of this concept. But in the meanwhile, I found some doubts in code.
When pool.assign(new TestWorkerThread()); executes in TestThreadPool Class, it calls
done.workerBegin(); method that is in Done Class, where it increments _activeThreads variable. But what I thinks is, LOGICALLY that is not correct because if number of threads are less(in this case 2) than number of tasks (given in TestThreadPool Class)(in this case 5), it increments _activeThreads (i.e., _activeThreads = 5) counts unnecessarily.
What _started variable does in Done class?
How waitDone() and waitBegin() (in Done Class ) performs their functioning? (It is good if you explain these two methods step by step.)
Code is as follows. I am arranging the codes according to its flow.
TestThreadPool Class :-
package hitesh;
/**
*
* #author jhamb
*/
public class TestThreadPool {
public static void main(String args[]) throws InterruptedException
{
ThreadPool pool = new ThreadPool(2);
for (int i = 1;i <= 5;i++) {
pool.assign(new TestWorkerThread());
}
System.out.println("All tasks are assigned");
pool.complete();
System.out.println("All tasks are done.");
}
}
TestWorkerThread Class :-
package hitesh;
/**
*
* #author jhamb
*/
/**
* This class shows an example worker thread that can
* be used with the thread pool. It demonstrates the main
* points that should be included in any worker thread. Use
* this as a starting point for your own threads.
*/
public class TestWorkerThread implements Runnable {
static private int count = 0;
private int taskNumber;
protected Done done;
/**
*
* #param done
*/
TestWorkerThread()
{
count++;
taskNumber = count;
//System.out.println("tasknumber ---> " + taskNumber);
}
public void run()
{
System.out.println("TWT run starts --> " + this.toString());
for (int i=0;i <= 100;i += 25) {
System.out.println("Task number: " + taskNumber +
",percent complete = " + i );
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
}
}
System.out.println("task for thread --> " + this.toString() + " completed");
}
}
ThreadPool Class :-
package hitesh;
/**
*
* #author jhamb
*/
import java.util.*;
/*
* This is the main class for the thread pool. You should
* create an instance of this class and assign tasks to it.
*/
public class ThreadPool {
protected Thread threads[] = null;
Collection assignments = new ArrayList(3);
protected Done done = new Done();
public ThreadPool(int size) throws InterruptedException
{
threads = new WorkerThread[size];
for (int i=0;i<threads.length;i++) {
threads[i] = new WorkerThread(this);
threads[i].start();
System.out.println ("thread " + i + " started");
threads[i].sleep(1000);
}
}
public synchronized void assign(Runnable r)
{
done.workerBegin();
assignments.add(r);
System.out.println("Collection size ---> " + assignments.size() + " Thread can work on this");
notify();
}
public synchronized Runnable getAssignment()
{
try {
while ( !assignments.iterator().hasNext() )
wait();
Runnable r = (Runnable)assignments.iterator().next();
assignments.remove(r);
return r;
} catch (InterruptedException e) {
done.workerEnd();
return null;
}
}
public void complete()
{
done.waitBegin();
done.waitDone();
}
}
WorkerThread Class :-
package hitesh;
import java.util.*;
/**
*
* #author jhamb
*/
/**
* The worker threads that make up the thread pool.
*/
class WorkerThread extends Thread {
/**
* True if this thread is currently processing.
*/
public boolean busy;
/**
* The thread pool that this object belongs to.
*/
public ThreadPool owner;
/**
* The constructor.
*
* #param o the thread pool
*/
WorkerThread(ThreadPool o)
{
owner = o;
}
/**
* Scan for and execute tasks.
*/
//#Override
public void run()
{
System.out.println("Threads name : "+ this.getName() + " working.....");
Runnable target = null;
do {
System.out.println("enter in do while " + this.getName() );
target = owner.getAssignment();
System.out.println("GetAssignment k aage aa gya mai " + target);
if (target!=null) {
target.run();
//target.
owner.done.workerEnd();
}
} while (target!=null);
System.out.println("do while finishes for "+ this.getName());
}
}
Done Class :-
package hitesh;
/**
*
* #author jhamb
*/
/**
*
* This is a thread pool for Java, it is
* simple to use and gets the job done. This program and
* all supporting files are distributed under the Limited
* GNU Public License (LGPL, http://www.gnu.org).
*
* This is a very simple object that
* allows the TheadPool to determine when
* it is done. This object implements
* a simple lock that the ThreadPool class
* can wait on to determine completion.
* Done is defined as the ThreadPool having
* no more work to complete.
*
* Copyright 2001 by Jeff Heaton
*
* #author Jeff Heaton (http://www.jeffheaton.com)
* #version 1.0
*/
public class Done {
/**
* The number of Worker object
* threads that are currently working
* on something.
*/
private int _activeThreads = 0;
/**
* This boolean keeps track of if
* the very first thread has started
* or not. This prevents this object
* from falsely reporting that the ThreadPool
* is done, just because the first thread
* has not yet started.
*/
private boolean _started = false;
/**
* This method can be called to block
* the current thread until the ThreadPool
* is done.
*/
synchronized public void waitDone()
{
try {
while ( _activeThreads>0 ) {
wait();
}
} catch ( InterruptedException e ) {
}
}
/**
* Called to wait for the first thread to
* start. Once this method returns the
* process has begun.
*/
synchronized public void waitBegin()
{
try {
while ( !_started ) {
wait();
}
} catch ( InterruptedException e ) {
}
}
/**
* Called by a Worker object
* to indicate that it has begun
* working on a workload.
*/
synchronized public void workerBegin()
{
_activeThreads++;
_started = true;
notify();
}
/**
* Called by a Worker object to
* indicate that it has completed a
* workload.
*/
synchronized public void workerEnd()
{
_activeThreads--;
notify();
}
/**
* Called to reset this object to
* its initial state.
*/
synchronized public void reset()
{
_activeThreads = 0;
}
}
Please help. Thanks in advance. Looking for your kind response.
Now I understand that whole code very perfectly. If you find any doubts in this code, then you can ask.
Answers of my questions are as follows after reading a lot on this.
Yes, you are right, it is logically wrong. Its better, if it would be _activeTasks . It is used to kill all the threads , when threadpool have no more work because waitDone() function executes successfully only when _activeTasks <= 0.
This Variable is used in waitBegin() method. Whenever any tasks starts, it updates _started by TRUE, means the tasks that are assigned by users is now in processing by threads, means threads starts working on these tasks. If tasks is not given by user, then all threads are still active , and waiting for tasks. This is the use of this variable here.
waitBegin() method executes successfully when threads starts working on tasks, because in that case only _started become true. Otherwise, threads keep on waiting for some tasks. waitDone() executes successfully only when _activeTasks become Zero, because this is the only situation when threadpool don't have any work to perform, means threadpool completes its work. Otherwise, it keep waiting until all tasks finish, means it waits until when _activeTasks becomes ZERO
I want to detect when some time consumption operations in main thread cause gui freeze.
My target is to set and unset wait cursor automatically.
thanks
I think you're putting the cart before the horse: Your main thread shouldn't do any time-consuming operations in the first place - they should always be externalized in separate threads, so that your GUI can stay responsive (and e.g. show status on the operations, or provide the possibility to abort them).
I think this could be helpful: http://www.javaspecialists.eu/archive/Issue075.html and http://www.javaworld.com/javaworld/javatips/jw-javatip87.html.
You can have a thread which polls the GUI thread's stack trace to determine whether it is idle or busy. If busy too often, you can log what it is doing (the stack trace) to a log. Initially it might be interesting to record every non-idle stack trace and work out which ones are not worth logging.
This EDT lockup detection code will do the job by adding watch dogs.
EventQueueWithWD.java:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* Alternative events dispatching queue. The benefit over the
* default Event Dispatch queue is that you can add as many
* watchdog timers as you need and they will trigger arbitrary
* actions when processing of single event will take longer than
* one timer period.
* <p/>
* Timers can be of two types:
* <ul>
* <li><b>Repetitive</b> - action can be triggered multiple times
* for the same "lengthy" event dispatching.
* </li>
* <li><b>Non-repetitive</b> - action can be triggered only once
* per event dispatching.</li>
* </ul>
* <p/>
* The queue records time of the event dispatching start. This
* time is used by the timers to check if dispatching takes
* longer than their periods. If so the timers trigger associated
* actions.
* <p/>
* In order to use this queue application should call
* <code>install()</code> method. This method will create,
* initialize and register the alternative queue as appropriate.
* It also will return the instance of the queue for further
* interactions. Here's an example of how it can be done:
* <p/>
* <pre>
* <p/>
* EventQueueWithWD queue = EventQueueWithWD.install();
* Action edtOverloadReport = ...;
* <p/>
* // install single-shot wg to report EDT overload after
* // 10-seconds timeout
* queue.addWatchdog(10000, edtOverloadReport, false);
* <p/>
* </pre>
*/
public class EventQueueWithWD extends EventQueue {
// Main timer
private final java.util.Timer timer = new java.util.Timer(true);
// Group of informational fields for describing the event
private final Object eventChangeLock = new Object();
private volatile long eventDispatchingStart = -1;
private volatile AWTEvent event = null;
/**
* Hidden utility constructor.
*/
private EventQueueWithWD() { }
/**
* Install alternative queue.
*
* #return instance of queue installed.
*/
public static EventQueueWithWD install() {
EventQueue eventQueue =
Toolkit.getDefaultToolkit().getSystemEventQueue();
EventQueueWithWD newEventQueue = new EventQueueWithWD();
eventQueue.push(newEventQueue);
return newEventQueue;
}
/**
* Record the event and continue with usual dispatching.
*
* #param anEvent event to dispatch.
*/
protected void dispatchEvent(AWTEvent anEvent) {
setEventDispatchingStart(anEvent, System.currentTimeMillis());
super.dispatchEvent(anEvent);
setEventDispatchingStart(null, -1);
}
/**
* Register event and dispatching start time.
*
* #param anEvent event.
* #param timestamp dispatching start time.
*/
private void setEventDispatchingStart(AWTEvent anEvent,
long timestamp) {
synchronized (eventChangeLock) {
event = anEvent;
eventDispatchingStart = timestamp;
}
}
/**
* Add watchdog timer. Timer will trigger <code>listener</code>
* if the queue dispatching event longer than specified
* <code>maxProcessingTime</code>. If the timer is
* <code>repetitive</code> then it will trigger additional
* events if the processing 2x, 3x and further longer than
* <code>maxProcessingTime</code>.
*
* #param maxProcessingTime maximum processing time.
* #param listener listener for events. The listener
* will receive <code>AWTEvent</code>
* as source of event.
* #param repetitive TRUE to trigger consequent events
* for 2x, 3x and further periods.
*/
public void addWatchdog(long maxProcessingTime,
ActionListener listener,
boolean repetitive) {
Watchdog checker = new Watchdog(maxProcessingTime, listener,
repetitive);
timer.schedule(checker, maxProcessingTime,
maxProcessingTime);
}
/**
* Checks if the processing of the event is longer than the
* specified <code>maxProcessingTime</code>. If so then
* listener is notified.
*/
private class Watchdog extends TimerTask {
// Settings
private final long maxProcessingTime;
private final ActionListener listener;
private final boolean repetitive;
// Event reported as "lengthy" for the last time. Used to
// prevent repetitive behaviour in non-repeatitive timers.
private AWTEvent lastReportedEvent = null;
/**
* Creates timer.
*
* #param maxProcessingTime maximum event processing time
* before listener is notified.
* #param listener listener to notify.
* #param repetitive TRUE to allow consequent
* notifications for the same event
*/
private Watchdog(long maxProcessingTime,
ActionListener listener,
boolean repetitive) {
if (listener == null)
throw new IllegalArgumentException(
"Listener cannot be null.");
if (maxProcessingTime < 0)
throw new IllegalArgumentException(
"Max locking period should be greater than zero");
this.maxProcessingTime = maxProcessingTime;
this.listener = listener;
this.repetitive = repetitive;
}
public void run() {
long time;
AWTEvent currentEvent;
// Get current event requisites
synchronized (eventChangeLock) {
time = eventDispatchingStart;
currentEvent = event;
}
long currentTime = System.currentTimeMillis();
// Check if event is being processed longer than allowed
if (time != -1 && (currentTime - time > maxProcessingTime) &&
(repetitive || currentEvent != lastReportedEvent)) {
listener.actionPerformed(
new ActionEvent(currentEvent, -1, null));
lastReportedEvent = currentEvent;
}
}
}
}
SampleEQUsage.java:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.Date;
/**
* Sample usage of <code>EventQueueWithWD</code> class.
*/
public class SampleEQUsage extends JFrame
{
public SampleEQUsage()
{
super("Sample EQ Usage");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().add(new JButton(new AbstractAction("Go")
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println(new Date());
try
{
// Sleep for 10 seconds
Thread.sleep(10000);
} catch (InterruptedException e1)
{
}
}
}));
setSize(100, 100);
}
public static void main(String[] args)
{
initQueue();
SampleEQUsage sequ = new SampleEQUsage();
sequ.setVisible(true);
}
// Install and init the alternative queue
private static void initQueue()
{
EventQueueWithWD queue = EventQueueWithWD.install();
// Install 3-seconds single-shot watchdog timer
queue.addWatchdog(3000, new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
System.out.println(new Date() + " 3 seconds - single-shot");
}
}, false);
// Install 3-seconds multi-shot watchdog timer
queue.addWatchdog(3000, new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
System.out.println(new Date() + " 3 seconds - multi-shot");
}
}, true);
// Install 11-seconds multi-shot watchdog timer
queue.addWatchdog(11000, new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
System.out.println(new Date() + " 11 seconds - multi-shot");
}
}, true);
}
}
I need something which is directly equivalent to CountDownLatch, but is resettable (remaining thread-safe!). I can't use classic synchronisation constructs as they simply don't work in this situation (complex locking issues). At the moment, I'm creating many CountDownLatch objects, each replacing the previous one. I believe this is doing in the young generation in the GC (due to the sheer number of objects). You can see the code which uses the latches below (it's part of the java.net mock for a ns-3 network simulator interface).
Some ideas might be to try CyclicBarrier (JDK5+) or Phaser (JDK7)
I can test code and get back to anyone that finds a solution to this problem, since I'm the only one who can insert it into the running system to see what happens :)
/**
*
*/
package kokunet;
import java.io.IOException;
import java.nio.channels.ClosedSelectorException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import kokuks.IConnectionSocket;
import kokuks.KKSAddress;
import kokuks.KKSSocket;
import kokuks.KKSSocketListener;
/**
* KSelector
* #version 1.0
* #author Chris Dennett
*/
public class KSelector extends SelectorImpl {
// True if this Selector has been closed
private volatile boolean closed = false;
// Lock for close and cleanup
final class CloseLock {}
private final Object closeLock = new CloseLock();
private volatile boolean selecting = false;
private volatile boolean wakeup = false;
class SocketListener implements KKSSocketListener {
protected volatile CountDownLatch latch = null;
/**
*
*/
public SocketListener() {
newLatch();
}
protected synchronized CountDownLatch newLatch() {
return this.latch = new CountDownLatch(1);
}
protected synchronized void refreshReady(KKSSocket socket) {
if (!selecting) return;
synchronized (socketToChannel) {
SelChImpl ch = socketToChannel.get(socket);
if (ch == null) {
System.out.println("ks sendCB: channel not found for socket: " + socket);
return;
}
synchronized (channelToKey) {
SelectionKeyImpl sk = channelToKey.get(ch);
if (sk != null) {
if (handleSelect(sk)) {
latch.countDown();
}
}
}
}
}
#Override
public void connectionSucceeded(KKSSocket socket) {
refreshReady(socket);
}
#Override
public void connectionFailed(KKSSocket socket) {
refreshReady(socket);
}
#Override
public void dataSent(KKSSocket socket, long bytesSent) {
refreshReady(socket);
}
#Override
public void sendCB(KKSSocket socket, long bytesAvailable) {
refreshReady(socket);
}
#Override
public void onRecv(KKSSocket socket) {
refreshReady(socket);
}
#Override
public void newConnectionCreated(KKSSocket socket, KKSSocket newSocket, KKSAddress remoteaddress) {
refreshReady(socket);
}
#Override
public void normalClose(KKSSocket socket) {
wakeup();
}
#Override
public void errorClose(KKSSocket socket) {
wakeup();
}
}
protected final Map<KKSSocket, SelChImpl> socketToChannel = new HashMap<KKSSocket, SelChImpl>();
protected final Map<SelChImpl, SelectionKeyImpl> channelToKey = new HashMap<SelChImpl, SelectionKeyImpl>();
protected final SocketListener currListener = new SocketListener();
protected Thread selectingThread = null;
SelChImpl getChannelForSocket(KKSSocket s) {
synchronized (socketToChannel) {
return socketToChannel.get(s);
}
}
SelectionKeyImpl getSelKeyForChannel(KKSSocket s) {
synchronized (channelToKey) {
return channelToKey.get(s);
}
}
protected boolean markRead(SelectionKeyImpl impl) {
synchronized (impl) {
if (!impl.isValid()) return false;
impl.nioReadyOps(impl.readyOps() | SelectionKeyImpl.OP_READ);
return selectedKeys.add(impl);
}
}
protected boolean markWrite(SelectionKeyImpl impl) {
synchronized (impl) {
if (!impl.isValid()) return false;
impl.nioReadyOps(impl.readyOps() | SelectionKeyImpl.OP_WRITE);
return selectedKeys.add(impl);
}
}
protected boolean markAccept(SelectionKeyImpl impl) {
synchronized (impl) {
if (!impl.isValid()) return false;
impl.nioReadyOps(impl.readyOps() | SelectionKeyImpl.OP_ACCEPT);
return selectedKeys.add(impl);
}
}
protected boolean markConnect(SelectionKeyImpl impl) {
synchronized (impl) {
if (!impl.isValid()) return false;
impl.nioReadyOps(impl.readyOps() | SelectionKeyImpl.OP_CONNECT);
return selectedKeys.add(impl);
}
}
/**
* #param provider
*/
protected KSelector(SelectorProvider provider) {
super(provider);
}
/* (non-Javadoc)
* #see kokunet.SelectorImpl#implClose()
*/
#Override
protected void implClose() throws IOException {
provider().getApp().printMessage("implClose: closed: " + closed);
synchronized (closeLock) {
if (closed) return;
closed = true;
for (SelectionKey sk : keys) {
provider().getApp().printMessage("dereg1");
deregister((AbstractSelectionKey)sk);
provider().getApp().printMessage("dereg2");
SelectableChannel selch = sk.channel();
if (!selch.isOpen() && !selch.isRegistered())
((SelChImpl)selch).kill();
}
implCloseInterrupt();
}
}
protected void implCloseInterrupt() {
wakeup();
}
private boolean handleSelect(SelectionKey k) {
synchronized (k) {
boolean notify = false;
if (!k.isValid()) {
k.cancel();
((SelectionKeyImpl)k).channel.socket().removeListener(currListener);
return false;
}
SelectionKeyImpl ski = (SelectionKeyImpl)k;
if ((ski.interestOps() & SelectionKeyImpl.OP_READ) != 0) {
if (ski.channel.socket().getRxAvailable() > 0) {
notify |= markRead(ski);
}
}
if ((ski.interestOps() & SelectionKeyImpl.OP_WRITE) != 0) {
if (ski.channel.socket().getTxAvailable() > 0) {
notify |= markWrite(ski);
}
}
if ((ski.interestOps() & SelectionKeyImpl.OP_CONNECT) != 0) {
if (!ski.channel.socket().isConnectionless()) {
IConnectionSocket cs = (IConnectionSocket)ski.channel.socket();
if (!ski.channel.socket().isAccepting() && !cs.isConnecting() && !cs.isConnected()) {
notify |= markConnect(ski);
}
}
}
if ((ski.interestOps() & SelectionKeyImpl.OP_ACCEPT) != 0) {
//provider().getApp().printMessage("accept check: ski: " + ski + ", connectionless: " + ski.channel.socket().isConnectionless() + ", listening: " + ski.channel.socket().isListening() + ", hasPendingConn: " + (ski.channel.socket().isConnectionless() ? "nope!" : ((IConnectionSocket)ski.channel.socket()).hasPendingConnections()));
if (!ski.channel.socket().isConnectionless() && ski.channel.socket().isListening()) {
IConnectionSocket cs = (IConnectionSocket)ski.channel.socket();
if (cs.hasPendingConnections()) {
notify |= markAccept(ski);
}
}
}
return notify;
}
}
private boolean handleSelect() {
boolean notify = false;
// get initial status
for (SelectionKey k : keys) {
notify |= handleSelect(k);
}
return notify;
}
/* (non-Javadoc)
* #see kokunet.SelectorImpl#doSelect(long)
*/
#Override
protected int doSelect(long timeout) throws IOException {
processDeregisterQueue();
long timestartedms = System.currentTimeMillis();
synchronized (selectedKeys) {
synchronized (currListener) {
wakeup = false;
selectingThread = Thread.currentThread();
selecting = true;
}
try {
handleSelect();
if (!selectedKeys.isEmpty() || timeout == 0) {
return selectedKeys.size();
}
//TODO: useless op if we have keys available
for (SelectionKey key : keys) {
((SelectionKeyImpl)key).channel.socket().addListener(currListener);
}
try {
while (!wakeup && isOpen() && selectedKeys.isEmpty()) {
CountDownLatch latch = null;
synchronized (currListener) {
if (wakeup || !isOpen() || !selectedKeys.isEmpty()) {
break;
}
latch = currListener.newLatch();
}
try {
if (timeout > 0) {
long currtimems = System.currentTimeMillis();
long remainingMS = (timestartedms + timeout) - currtimems;
if (remainingMS > 0) {
latch.await(remainingMS, TimeUnit.MILLISECONDS);
} else {
break;
}
} else {
latch.await();
}
} catch (InterruptedException e) {
}
}
return selectedKeys.size();
} finally {
for (SelectionKey key : keys) {
((SelectionKeyImpl)key).channel.socket().removeListener(currListener);
}
}
} finally {
synchronized (currListener) {
selecting = false;
selectingThread = null;
wakeup = false;
}
}
}
}
/* (non-Javadoc)
* #see kokunet.SelectorImpl#implRegister(kokunet.SelectionKeyImpl)
*/
#Override
protected void implRegister(SelectionKeyImpl ski) {
synchronized (closeLock) {
if (closed) throw new ClosedSelectorException();
synchronized (channelToKey) {
synchronized (socketToChannel) {
keys.add(ski);
socketToChannel.put(ski.channel.socket(), ski.channel);
channelToKey.put(ski.channel, ski);
}
}
}
}
/* (non-Javadoc)
* #see kokunet.SelectorImpl#implDereg(kokunet.SelectionKeyImpl)
*/
#Override
protected void implDereg(SelectionKeyImpl ski) throws IOException {
synchronized (channelToKey) {
synchronized (socketToChannel) {
keys.remove(ski);
socketToChannel.remove(ski.channel.socket());
channelToKey.remove(ski.channel);
SelectableChannel selch = ski.channel();
if (!selch.isOpen() && !selch.isRegistered())
((SelChImpl)selch).kill();
}
}
}
/* (non-Javadoc)
* #see kokunet.SelectorImpl#wakeup()
*/
#Override
public Selector wakeup() {
synchronized (currListener) {
if (selecting) {
wakeup = true;
selecting = false;
selectingThread.interrupt();
selectingThread = null;
}
}
return this;
}
}
Cheers,
Chris
I copied CountDownLatch and implemented a reset() method that resets the internal Sync class to its initial state (starting count) :) Appears to work fine. No more unnecessary object creation \o/ It was not possible to subclass because sync was private. Boo.
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* A synchronization aid that allows one or more threads to wait until
* a set of operations being performed in other threads completes.
*
* <p>A {#code CountDownLatch} is initialized with a given <em>count</em>.
* The {#link #await await} methods block until the current count reaches
* zero due to invocations of the {#link #countDown} method, after which
* all waiting threads are released and any subsequent invocations of
* {#link #await await} return immediately. This is a one-shot phenomenon
* -- the count cannot be reset. If you need a version that resets the
* count, consider using a {#link CyclicBarrier}.
*
* <p>A {#code CountDownLatch} is a versatile synchronization tool
* and can be used for a number of purposes. A
* {#code CountDownLatch} initialized with a count of one serves as a
* simple on/off latch, or gate: all threads invoking {#link #await await}
* wait at the gate until it is opened by a thread invoking {#link
* #countDown}. A {#code CountDownLatch} initialized to <em>N</em>
* can be used to make one thread wait until <em>N</em> threads have
* completed some action, or some action has been completed N times.
*
* <p>A useful property of a {#code CountDownLatch} is that it
* doesn't require that threads calling {#code countDown} wait for
* the count to reach zero before proceeding, it simply prevents any
* thread from proceeding past an {#link #await await} until all
* threads could pass.
*
* <p><b>Sample usage:</b> Here is a pair of classes in which a group
* of worker threads use two countdown latches:
* <ul>
* <li>The first is a start signal that prevents any worker from proceeding
* until the driver is ready for them to proceed;
* <li>The second is a completion signal that allows the driver to wait
* until all workers have completed.
* </ul>
*
* <pre>
* class Driver { // ...
* void main() throws InterruptedException {
* CountDownLatch startSignal = new CountDownLatch(1);
* CountDownLatch doneSignal = new CountDownLatch(N);
*
* for (int i = 0; i < N; ++i) // create and start threads
* new Thread(new Worker(startSignal, doneSignal)).start();
*
* doSomethingElse(); // don't let run yet
* startSignal.countDown(); // let all threads proceed
* doSomethingElse();
* doneSignal.await(); // wait for all to finish
* }
* }
*
* class Worker implements Runnable {
* private final CountDownLatch startSignal;
* private final CountDownLatch doneSignal;
* Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
* this.startSignal = startSignal;
* this.doneSignal = doneSignal;
* }
* public void run() {
* try {
* startSignal.await();
* doWork();
* doneSignal.countDown();
* } catch (InterruptedException ex) {} // return;
* }
*
* void doWork() { ... }
* }
*
* </pre>
*
* <p>Another typical usage would be to divide a problem into N parts,
* describe each part with a Runnable that executes that portion and
* counts down on the latch, and queue all the Runnables to an
* Executor. When all sub-parts are complete, the coordinating thread
* will be able to pass through await. (When threads must repeatedly
* count down in this way, instead use a {#link CyclicBarrier}.)
*
* <pre>
* class Driver2 { // ...
* void main() throws InterruptedException {
* CountDownLatch doneSignal = new CountDownLatch(N);
* Executor e = ...
*
* for (int i = 0; i < N; ++i) // create and start threads
* e.execute(new WorkerRunnable(doneSignal, i));
*
* doneSignal.await(); // wait for all to finish
* }
* }
*
* class WorkerRunnable implements Runnable {
* private final CountDownLatch doneSignal;
* private final int i;
* WorkerRunnable(CountDownLatch doneSignal, int i) {
* this.doneSignal = doneSignal;
* this.i = i;
* }
* public void run() {
* try {
* doWork(i);
* doneSignal.countDown();
* } catch (InterruptedException ex) {} // return;
* }
*
* void doWork() { ... }
* }
*
* </pre>
*
* <p>Memory consistency effects: Actions in a thread prior to calling
* {#code countDown()}
* <i>happen-before</i>
* actions following a successful return from a corresponding
* {#code await()} in another thread.
*
* #since 1.5
* #author Doug Lea
*/
public class ResettableCountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
public final int startCount;
Sync(int count) {
this.startCount = count;
setState(startCount);
}
int getCount() {
return getState();
}
public int tryAcquireShared(int acquires) {
return getState() == 0? 1 : -1;
}
public boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
public void reset() {
setState(startCount);
}
}
private final Sync sync;
/**
* Constructs a {#code CountDownLatch} initialized with the given count.
*
* #param count the number of times {#link #countDown} must be invoked
* before threads can pass through {#link #await}
* #throws IllegalArgumentException if {#code count} is negative
*/
public ResettableCountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {#linkplain Thread#interrupt interrupted}.
*
* <p>If the current count is zero then this method returns immediately.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of two things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {#link #countDown} method; or
* <li>Some other thread {#linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {#linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {#link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* #throws InterruptedException if the current thread is interrupted
* while waiting
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void reset() {
sync.reset();
}
/**
* Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {#linkplain Thread#interrupt interrupted},
* or the specified waiting time elapses.
*
* <p>If the current count is zero then this method returns immediately
* with the value {#code true}.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of three things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {#link #countDown} method; or
* <li>Some other thread {#linkplain Thread#interrupt interrupts}
* the current thread; or
* <li>The specified waiting time elapses.
* </ul>
*
* <p>If the count reaches zero then the method returns with the
* value {#code true}.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {#linkplain Thread#interrupt interrupted} while waiting,
* </ul>
* then {#link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* <p>If the specified waiting time elapses then the value {#code false}
* is returned. If the time is less than or equal to zero, the method
* will not wait at all.
*
* #param timeout the maximum time to wait
* #param unit the time unit of the {#code timeout} argument
* #return {#code true} if the count reached zero and {#code false}
* if the waiting time elapsed before the count reached zero
* #throws InterruptedException if the current thread is interrupted
* while waiting
*/
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* <p>If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
* <p>If the current count equals zero then nothing happens.
*/
public void countDown() {
sync.releaseShared(1);
}
/**
* Returns the current count.
*
* <p>This method is typically used for debugging and testing purposes.
*
* #return the current count
*/
public long getCount() {
return sync.getCount();
}
/**
* Returns a string identifying this latch, as well as its state.
* The state, in brackets, includes the String {#code "Count ="}
* followed by the current count.
*
* #return a string identifying this latch, as well as its state
*/
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
Phaser has more options, we can implement resettable countdownLatch using that.
Please read below basic concepts from the following sites
https://examples.javacodegeeks.com/core-java/util/concurrent/phaser/java-util-concurrent-phaser-example/
http://netjs.blogspot.in/2016/01/phaser-in-java-concurrency.html
import java.util.concurrent.Phaser;
/**
* Resettable countdownLatch using phaser
*/
public class PhaserExample {
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser(3); // you can use constructor hint or
// register() or mixture of both
// register self... so parties are incremented to 4 (3+1) now
phaser.register();
//register is one time call for all the phases.
//means no need to register for every phase
int phasecount = phaser.getPhase();
System.out.println("Phasecount is " + phasecount);
new PhaserExample().testPhaser(phaser, 2000);
new PhaserExample().testPhaser(phaser, 4000);
new PhaserExample().testPhaser(phaser, 6000);
// similar to await() in countDownLatch/CyclicBarrier
// parties are decremented to 3 (4+1) now
phaser.arriveAndAwaitAdvance();
// once all the thread arrived at same level, barrier opens
System.out.println("Barrier has broken.");
phasecount = phaser.getPhase();
System.out.println("Phasecount is " + phasecount);
//second phase
new PhaserExample().testPhaser(phaser, 2000);
new PhaserExample().testPhaser(phaser, 4000);
new PhaserExample().testPhaser(phaser, 6000);
phaser.arriveAndAwaitAdvance();
// once all the thread arrived at same level, barrier opens
System.out.println("Barrier has broken.");
phasecount = phaser.getPhase();
System.out.println("Phasecount is " + phasecount);
}
private void testPhaser(final Phaser phaser, final int sleepTime) {
// phaser.register(); //Already constructor hint is given so not
// required
new Thread() {
#Override
public void run() {
try {
Thread.sleep(sleepTime);
System.out.println(Thread.currentThread().getName() + " arrived");
// phaser.arrive(); //similar to CountDownLatch#countDown()
phaser.arriveAndAwaitAdvance();// thread will wait till Barrier opens
// arriveAndAwaitAdvance is similar to CyclicBarrier#await()
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " after passing barrier");
}
}.start();
}
}
Based on #Fidel -s answer, I made a drop-in replacement for ResettableCountDownLatch. The changes I made
mLatch is private volatile
mInitialCount is private final
the return type of the simple await() has changed to void.
Otherwise, the original code is cool too. So, this is the full, enhanced code:
public class ResettableCountDownLatch {
private final int initialCount;
private volatile CountDownLatch latch;
public ResettableCountDownLatch(int count) {
initialCount = count;
latch = new CountDownLatch(count);
}
public void reset() {
latch = new CountDownLatch(initialCount);
}
public void countDown() {
latch.countDown();
}
public void await() throws InterruptedException {
latch.await();
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return latch.await(timeout, unit);
}
}
Update
Based on #Systemplanet-s comment, here is a safer version of reset():
// An atomic reference is required because reset() is not that atomic anymore, not even with `volatile`.
private final AtomicReference<CountDownLatch> latchHolder = new AtomicReference<>();
public void reset() {
// obtaining a local reference for modifying the required latch
final CountDownLatch oldLatch = latchHolder.getAndSet(null);
if (oldLatch != null) {
// checking the count each time to prevent unnecessary countdowns due to parallel countdowns
while (0L < oldLatch.getCount()) {
oldLatch.countDown();
}
}
}
Basically, it's a choice between simplicity and safety. I.e. if you are willing to move the responsibility to the client of your code, then it's enough to set the reference null in reset().
On the other hand, if you want to make it easy for the users of this code, then you need to use a little more tricks.
I'm not sure if this is fatally flawed but I recently had the same problem and solved it by simply instantiating a new CountDownLatch object each time I wanted to reset. Something like this:
Waiter:
bla();
latch.await();
//now the latch has counted down to 0
blabla();
CountDowner
foo();
latch.countDown();
//now the latch has counted down to 0
latch = new CountDownLatch(1);
Waiter.receiveReferenceToNewLatch(latch);
bar();
Obviously this is a heavy abstraction but thus far it has worked for me and doesn't require you to tinker with any class definitions.
Use Phaser.
if only one thread should to do work. U can join AtomicBoolean and Phaser
AtomicBoolean someConditionInProgress = new AtomicBoolean("false"); Phaser onConditionalPhaser = new Phaser(1);
(some function) if (!someConditionInProgress.compareAndSet(false, true)) {
try {
//do something
} finally {
someConditionInProgress.set(false);
//release barier
onConditionalPhaser.arrive();
}
} else {
onConditionalPhaser.awaitAdvance(onConditionalPhaser.getPhase());
}
Looks like you want to turn asynchronous I/O to synchronous. The whole idea of using asynchronous I/O is to avoid threads, but CountDownLatch requres using threads. This is an obvious contradiction in your question. So, you can:
keep using threads and employ synchronous I/O instead of Selectors and the suff. This will be much more simple and reliable
keep using asynchronous I/0 and give up CountDownLatch. Then you need an asynchronous library - look at RxJava, Akka, or df4j.
continue to develop your project for fun. Then you can try to use java.util.Semaphore instead of CountDownLatch, or program your own synchronization class using synchronized/wait/notify.
public class ResettableLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return getState() == 0 ? 1 : -1;
}
public void reset(int count) {
setState(count);
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
public ResettableLatch(int count) {
if (count < 0)
throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public void reset(int count) {
sync.reset(count);
}
}
This worked for me.
From what I was able to understand from the OP explanation and source code, the resettable CountDownLatch is not quite adequate concept for the problem he was going to solve. The documentation of the CountDownLatch itself mentions the OP's use case as simple gate initialized with a count of one:
CountDownLatch initialized with a count of one serves as a simple
on/off latch, or gate: all threads invoking await wait at the gate
until it is opened by a thread invoking countDown.
, but CountDownLatch implementation does not go any further in this direction.
So, myself having a problem similar to that of OP's I decided to introduce a SimpleGate class with the following properties:
Number of permits is one, which means it can be either in On or Off state;
There is a dedicated thread, called Gate Keeper that is only allowed to shut off or open up the Gate;
The right of gate keeping is transferable;
the opening up the Gate immediately allows the threads, that tried to come through the Gate, to do it (this very logical feature has been overlooked in the other answers);
as the thread contention is expected to be high, fairness is supported as an option, this allows to decrease an effect of thread barging.
public class SimpleGate {
private static class Sync extends AbstractQueuedSynchronizer {
// State
private static final int SHUT = 1;
private static final int OPEN = 0;
private boolean fair;
public void setFair(boolean fair) {
this.fair = fair;
}
public void shutOff() {
super.setState(SHUT);
}
#Override
protected int tryAcquireShared(int arg) {
if (fair && super.hasQueuedPredecessors())
return -1;
return super.getState() == OPEN ? 1 : -1;
}
#Override
protected boolean tryReleaseShared(int arg) {
super.setState(OPEN);
return true;
}
}
private Sync sync = new Sync();
private volatile Thread gateKeeper = Thread.currentThread();
public SimpleGate(){
this(true);
}
public SimpleGate(boolean shutOff){
this(shutOff, false);
}
public SimpleGate(boolean shutOff, boolean fair){
if (shutOff)
sync.shutOff();
sync.setFair(fair);
}
public void comeThrough(){
if (Thread.currentThread() == gateKeeper)
throw new IllegalStateException("Gate Keeper thread is not supposed to come through the gate");
sync.acquireShared(0);
}
public void shutOff(){
if (Thread.currentThread() != gateKeeper)
throw new IllegalStateException("Only a Gate Keeper thread is allowed to shut off");
sync.shutOff();
}
public void openUp(){
if (Thread.currentThread() != gateKeeper)
throw new IllegalStateException("Only a Gate Keeper thread is allowed to open up");
sync.releaseShared(0);
}
public void transferOwnership(Thread newGateKeeper){
this.gateKeeper = newGateKeeper;
}
// an addition of waiting interruptibly and waiting for specified amount of time,
//if they are needed, is trivial
}
Another drop-in replacement
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ResettableCountDownLatch {
int mInitialCount;
CountDownLatch mLatch;
public ResettableCountDownLatch(int count) {
mInitialCount = count;
mLatch = new CountDownLatch(count);
}
public void reset() {
mLatch = new CountDownLatch(mInitialCount);
}
public void countDown() {
mLatch.countDown();
}
public boolean await() throws InterruptedException {
boolean result = mLatch.await();
return result;
}
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
boolean result = mLatch.await(timeout, unit);
return result;
}
}