I have a pretty complex problem. In my current project, I have a GUI written in Java and a computing engine written in C++.
These are displays in Java which access to data in C++, and I have some issues with concurrency.
There are a long story in this code, so I can't just rewrite all (even if I want it occasionnaly :p).
When the engine modify the datas, it acquire a mutex. It's pretty clean from this side.
The problem is GUI. It is Java Swing, and it access the data without any control, from EventDispatchThread, or from any thread, and acquire the c++ mutex (via JNI) for each unitary access to the kernel (which is not good, for performance and data consistancy).
I already refactor it to encapsulate the lock code in Java in a "NativeMutex" which call the native functions lock and unlock from JNI.
I want write a "ReentrantNativeLock", to avoid rewrite all and just add some high-level lock.
But this ReentrantNativeLock must deal with the EventDisplayThread.
I have defined that this lock implementation must avoid that the EDT takes the mutex (by throwing an exception when the lock method is called from EDT), but just return when the lock is already owned by another thread (to deal with SwingUtilities.InvokeAndWait without rewrite all the dirty code of this application)
Conceptually, It's ok because I focus on synchronization between the C++ engine and the JAVA GUI, but it's not safe from the Java-side.
So I want to go further. If I can know which threads are waiting for EDT (which are the threads that have called "InvokeAndWait"), I can implement something safer. I will be able to check if the owner thread is waiting for EDT, and avoid some ununderstandable but probable bugs which will annoy my futur-myself and my coworker.
So, how can I know which threads are waiting for EDT (which are the threads that have called "InvokeAndWait")
(If I described the context, it's because I am open to listen other ideas which could solve my problem... Only if they doesn't imply a lot of rewrite.)
As some comments make me believe that the context isn't well described, I post some code which, I hope, will explicit my problem.
It's a basic Decorator, m_NativeLock
is the non-reentrant nativeLock.
public class ReentrantNativeLock implements NativeLock {
/**
* Logger
*/
private static final Logger LOGGER = Logger.getLogger(ReentrantNativeLock.class);
public ReentrantNativeLock(NativeLock adaptee) {
m_NativeLock = adaptee;
}
public void lock() {
if (!SwingUtilities.isEventDispatchThread()) {
m_ReentrantLock.lock();
if (m_ReentrantLock.getHoldCount() == 1) { // Only the first lock from a thread lock the engine, to avoid deadlock with the same thread
m_NativeLock.lock();
}
}
else if (m_ReentrantLock.isLocked()) {
// It's EDT, but some thread has lock the mutex, so it's ok... We assume that the locked thread as called SwingUtilities.invokeAndWait... But if I can check it, it will be better.
LOGGER.debug("Lock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
}
else {
// We try to avoid this case, so we throw an exception which will be tracked and avoided before release, if possible
throw new UnsupportedOperationException("L'EDT ne doit pas locker elle-même le moteur.");
}
}
public boolean tryLock() {
if (!SwingUtilities.isEventDispatchThread()) {
boolean result = m_ReentrantLock.tryLock();
if (result && m_ReentrantLock.getHoldCount() == 1) {
result = m_NativeLock.tryLock();// Only the first lock from a thread lock the engine, to avoid deadlock with the same thread
if (!result) {
m_ReentrantLock.unlock(); // If the trylock on engin fail, we free the lock (I will put it in a try{}finally{} if I valid this solution.
}
}
return result;
}
else if (m_ReentrantLock.isLocked()) {
// It's EDT, but some thread has lock the mutex, so it's ok... We assume that the locked thread as called SwingUtilities.invokeAndWait... But if I can check it, it will be better.
LOGGER.debug("Lock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
return true;
}
else {
// We try to avoid this case, so we throw an exception which will be tracked and avoided before release, if possible
throw new UnsupportedOperationException("L'EDT ne doit pas locker elle-même le moteur.");
}
}
public void unlock() {
if (!SwingUtilities.isEventDispatchThread()) {
if (m_ReentrantLock.getHoldCount() == 1) {
m_NativeLock.unlock();
}
m_ReentrantLock.unlock();
}
else {
LOGGER.debug("Unlock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
}
}
final ReentrantLock m_ReentrantLock = new ReentrantLock();
final NativeLock m_NativeLock;
}
What you can do is have your own EventQueue
that records events to dispatch, from which Thread
they are created and if the Thread
is waiting for the event to be dispatched (so, in case a Thread
invokes invokeAndWait
).
First, push your own queue:
ThreadTrackingEventQueue queue = new ThreadTrackingEventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);
In your implementation of the queue:
postEvent
, check if it's an InvocationEvent
and if it's waiting to be notified. In such case track the Thread
and the corresponding eventdispatchEvent
to unmark the calling thread as waiting for the EDT.Complete example (watch out, it sleeps on the EDT to make collisions happen, but it should never be done in an application):
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InvocationEvent;
import java.lang.reflect.Field;
import java.util.Hashtable;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestEventQueue {
private final ThreadTrackingEventQueue queue;
public static class ThreadTrackingEventQueue extends EventQueue {
private Field notifierField;
private Hashtable<AWTEvent, Thread> waitingThreads = new Hashtable<AWTEvent, Thread>();
public ThreadTrackingEventQueue() throws NoSuchFieldException, SecurityException {
notifierField = InvocationEvent.class.getDeclaredField("notifier");
notifierField.setAccessible(true);
}
@Override
public void postEvent(AWTEvent event) {
if (!SwingUtilities.isEventDispatchThread() && event.getClass() == InvocationEvent.class) {
try {
Object object = notifierField.get(event);
if (object != null) {
// This thread is waiting to be notified: record it
waitingThreads.put(event, Thread.currentThread());
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
super.postEvent(event);
}
@Override
protected void dispatchEvent(AWTEvent event) {
try {
super.dispatchEvent(event);
} finally {
if (event.getClass() == InvocationEvent.class) {
waitingThreads.remove(event);
}
}
}
public Hashtable<AWTEvent, Thread> getWaitingThreads() {
return waitingThreads;
}
}
public TestEventQueue(ThreadTrackingEventQueue queue) {
this.queue = queue;
}
private void initUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextArea textArea = new JTextArea(30, 80);
JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
start();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
frame.add(new JScrollPane(textArea));
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
Timer t = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Hashtable<AWTEvent, Thread> waitingThreads = (Hashtable<AWTEvent, Thread>) queue.getWaitingThreads().clone();
if (waitingThreads.size() > 0) {
for (Thread t : queue.getWaitingThreads().values()) {
textArea.append("Thread " + t.getName() + " is waiting for EDT\n");
}
} else {
textArea.append("No threads are waiting\n");
}
}
});
t.start();
}
protected void start() throws InterruptedException {
final Random random = new Random();
ExecutorService pool = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
pool.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.out.println("sleeping before invoke and wait");
Thread.sleep(random.nextInt(2000) + 200);
System.out.println("invoke and wait");
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
try {
System.out.println("sleeping on EDT, bwark :-(");
// Very very bad, but trying to make collisions
// happen
Thread.sleep(random.nextInt(200) + 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
return true;
}
});
}
System.out.println("Invoked all");
}
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
final ThreadTrackingEventQueue queue = new ThreadTrackingEventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
TestEventQueue test = new TestEventQueue(queue);
test.initUI();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}