我有一个非常复杂的问题。在我目前的项目中,我有一个用Java编写的GUI和一个用C ++编写的计算引擎。
这些是Java中用C ++访问数据的显示,我遇到了一些并发问题。
这段代码有一个很长的故事,所以我不能只重写所有内容(即使我想要它偶尔发生:p)。
当引擎修改数据时,它会获取互斥锁。从这一方面看起来很干净。
问题是GUI。它是Java Swing,它无需任何控制地从EventDispatchThread或任何线程访问数据,并获取c ++ mutex(通过JNI)以获得对内核的每次单一访问(这对于性能和数据包含性而言并不好)
我已经重构它将Java中的锁定代码封装在“NativeMutex”中,该“NativeMutex”调用本机函数锁定并从JNI解锁。
我想写一个“ReentrantNativeLock”,以避免重写所有并只添加一些高级锁。
但是这个ReentrantNativeLock必须处理EventDisplayThread。
我已经定义了这个锁实现必须避免EDT接受互斥锁(通过在从EDT调用lock方法时抛出异常),但只是当锁已经被另一个线程拥有时才返回(以处理SwingUtilities .InvokeAndWait,不重写此应用程序的所有脏代码)
从概念上讲,这很好,因为我专注于C ++引擎和JAVA GUI之间的同步,但它在Java方面并不安全。
所以我想更进一步。如果我可以知道哪些线程正在等待EDT(哪些线程称为“InvokeAndWait”),我可以实现更安全的东西。 我将能够检查所有者线程是否正在等待EDT,并避免一些不可理解但可能的错误,这些错误会惹恼我的未来 - 我和我的同事。
那么,我怎么知道哪些线程正在等待EDT(哪些是调用“InvokeAndWait”的线程)
(如果我描述了上下文,那是因为我愿意听取其他可以解决我的问题的想法......只有当他们不暗示重写时才会这样做。)
由于一些评论让我相信上下文没有得到很好的描述,我发布了一些代码,我希望这些代码能够明确我的问题。
这是一个基本的装饰器,m_NativeLock
是不可重入的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;
}
答案 0 :(得分:2)
您可以做的是拥有自己的EventQueue
来记录要发送的事件,Thread
创建它们以及Thread
是否在等待事件的发送(所以,如果Thread
调用invokeAndWait
)。
首先,推送自己的队列:
ThreadTrackingEventQueue queue = new ThreadTrackingEventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);
在你的队列实现中:
postEvent
,检查它是否为InvocationEvent
以及是否等待通知。在这种情况下,跟踪Thread
和相应的事件dispatchEvent
以取消将调用线程标记为等待EDT。完整示例(注意,它在EDT上睡觉以发生冲突,但绝不应该在应用程序中完成):
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();
}
}
});
}
}
答案 1 :(得分:1)
您写道:
事实上,一些开发人员已经在JAVA中编写了大量代码 访问可以从C ++中的线程更新的数据 我必须维护的应用程序。这些代码从中调用 不同的主题,包括EDT。
问题是访问数据的EDT。您可能需要对编写的代码进行一些更改,以便EDT 永远不会直接操作共享数据。这意味着EDT必须向其他一些线程提供与数据相关的任务:
如果EDT需要更改某些数据,它会创建一个新线程来完成这项工作。
如果某个主题需要更新GUI更改,则会调用InvokeLater()
或InvokeAndWait()
。
----------我的答案(第二版)----------
嘿,路径尽头还有一些灯光。让我们重构所有代码,以确保一次只有一个InvokeAndWait()
。怎么做?首先,您需要编写一个名为MyInvokeAndWait()
的新全局方法。此方法使用锁定以确保一次只有一个线程可以调用InvokeAndWait()
。然后使用IDE搜索所有InvokeAndWait()
并将其替换为MyInvokeAndWait()
。
现在,在MyInvokeAndWait()
内,确保在调用InvokeAndWait()
时,将原子变量threadId
设置为调用线程的id(注意调用InvokeAndWait()
将阻止调用线程)。完成InvokeAndWait()
后,threadId
将被清除。
这样,只要EDT访问数据,您就可以检查所有者线程是否与threadId
具有相同的ID。如果是这种情况,请让EDT完成其工作,否则抛出异常。
嗯......你不需要确保一次只有一个线程可以调用InvokeAndWait()
。您可以将所有调用线程ID添加到集合中,然后验证所有者线程ID是否在集合中。