У меня довольно сложная проблема. В моем текущем проекте у меня есть графический интерфейс, написанный на Java, и вычислительный движок, написанный на C ++.
Это дисплеи в Java, которые имеют доступ к данным в C ++, и у меня есть некоторые проблемы с параллелизмом.
В этом коде есть длинная история, поэтому я не могу просто переписать все (даже если я хочу это время от времени: p).
Когда двигатель изменяет данные, он приобретает мьютекс. С этой стороны довольно чисто.
Проблема в GUI. Это Java Swing, и он получает доступ к данным без какого-либо контроля из EventDispatchThread или из любого потока и получает мьютекс c ++ (через JNI) для каждого унитарного доступа к ядру (что не очень хорошо для производительности и согласованности данных). ,
Я уже реорганизовал его, чтобы инкапсулировать код блокировки в Java в «NativeMutex», который вызывает собственные функции блокировки и разблокировки из JNI.
Я хочу написать «ReentrantNativeLock», чтобы избежать перезаписи всего и просто добавить высокоуровневую блокировку.
Но этот ReentrantNativeLock должен иметь дело с EventDisplayThread.
Я определил, что эта реализация блокировки должна избегать того, чтобы EDT брал мьютекс (создавая исключение, когда метод блокировки вызывается из EDT), а просто возвращался, когда блокировка уже принадлежит другому потоку (чтобы иметь дело с SwingUtilities.InvokeAndWait без переписать весь грязный код этого приложения)
Концептуально, это нормально, потому что я сосредоточен на синхронизации между C ++ engine и 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;
}
Что вы можете сделать, это иметь свой собственный 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();
}
}
});
}
}
Вы написали:
Дело в том, что некоторые разработчики написали много кода на JAVA
который обращается к данным, которые могут быть обновлены из потока в C ++ в
приложение, которое я должен поддерживать. Эти коды вызываются из
разные темы, в том числе EDT.
Проблема в том, что EDT обращается к данным. Возможно, вам придется внести некоторые изменения в письменный код, чтобы EDT никогда напрямую манипулирует общими данными. Это означает, что EDT должен давать связанные с данными задачи некоторым другим потокам:
Если EDT необходимо изменить некоторые данные, он создает новый поток для выполнения работы.
Если поток должен обновить изменения в GUI, он вызывает либо InvokeLater()
или же InvokeAndWait()
,
———- Мой ответ (второе издание) ———-
Эй, в конце пути еще есть огни.
Давайте рефакторинг всего кода, чтобы убедиться, что есть только один InvokeAndWait()
вовремя. Как это сделать? Сначала вам нужно написать новый глобальный метод MyInvokeAndWait()
, Этот метод использует блокировку, чтобы гарантировать, что только один поток может одновременно вызывать InvokeAndWait()
, Затем используйте IDE для поиска всех InvokeAndWait()
и заменить их MyInvokeAndWait()
,
Теперь внутри MyInvokeAndWait()
убедитесь, что когда InvokeAndWait()
называется, атомная переменная threadId
устанавливается на идентификатор вызывающего потока (обратите внимание, что вызов InvokeAndWait()
заблокирует вызывающий поток). когда InvokeAndWait()
закончено, threadId
очищается
Таким образом, всякий раз, когда EDT обращается к данным, вы можете проверить, имеет ли поток владельца одинаковый идентификатор с threadId
, Если это так, позвольте EDT выполнить свою работу, в противном случае выведите исключение.
Ну … Вам не нужно гарантировать, что только один поток может одновременно вызывать InvokeAndWait()
, Вы можете добавить все идентификаторы потока вызова в коллекцию и затем убедиться, что идентификатор потока владельца находится в коллекции.