Java Swing EDT:如何知道哪些线程正在等待通过 SwingUtilities.invokeAndWait 执行 EventDisplay?

Java Swing EDT : How to know which threads are waiting the execution of an EventDisplay via SwingUtilities.invokeAndWait?

我有一个非常复杂的问题。在我当前的项目中,我有一个用 Java 编写的 GUI 和一个用 C++ 编写的计算引擎。

这些是 Java 中的显示,在 C++ 中访问数据,我遇到了一些并发问题。

这段代码有很长的故事,所以我不能全部重写(即使我偶尔想要它:p)。

当引擎修改数据时,它会获取一个互斥量。从这边看还算干净

问题是图形用户界面。它是 Java Swing,它在没有任何控制的情况下从 EventDispatchThread 或任何线程访问数据,并为每次对内核的单一访问获取 c++ 互斥锁(通过 JNI)(这对性能不利和数据一致性)。

我已经重构它以将 Java 中的锁定代码封装在一个 "NativeMutex" 中,它从 JNI 调用本机函数锁定和解锁。

我想写一个"ReentrantNativeLock",以避免全部重写,只添加一些高级锁。

但是这个ReentrantNativeLock必须和EventDisplayThread打交道。

我已经定义了这个锁的实现必须避免 EDT 获取互斥量(通过在从 EDT 调用锁方法时抛出异常),但是当锁已经被另一个人拥有时只是 return线程(处理SwingUtilities.InvokeAndWait而不重写本应用的所有脏代码)

从概念上讲,没关系,因为我专注于 C++ 引擎和 JAVA GUI 之间的同步,但从 Java 端来看它并不安全。

所以我想更进一步。如果我能知道哪些线程正在等待 EDT(哪些是调用了 "InvokeAndWait" 的线程),我就可以实现更安全的方法。 我将能够检查所有者线程是否正在等待 EDT,并避免一些无法理解但可能会惹恼我未来的我自己和我的同事的错误。

那么,我怎么知道哪些线程正在等待 EDT(哪些线程调用了 "InvokeAndWait")

(如果我描述了上下文,那是因为我乐于倾听其他可以解决我的问题的想法......前提是它们不意味着大量重写。)

由于一些评论让我认为上下文没有得到很好的描述,我 post 一些代码,我希望这些代码能够明确我的问题。

是基本的Decorator,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;
}

您写道:

The fact is that some developers have written a lot of code in JAVA which accesses to data that could be updated from a thread in C++ in an application that I have to maintain. These codes are called from different threads, including EDT.

问题出在访问数据的 EDT 上。您可能需要对编写的代码进行一些更改,以便 EDT never 直接操作共享数据。这意味着EDT必须将与数据相关的任务交给其他一些线程:

  • 如果 EDT 需要更改一些数据,它会创建一个新线程来完成这项工作。

  • 如果线程需要更新对 GUI 的更改,它会调用 InvokeLater()InvokeAndWait()

------------我的答案(第二版)------------

嘿,小路的尽头还有一些灯火

  1. 让我们重构所有代码以确保一次只有一个 InvokeAndWait()。怎么做?首先,您需要编写一个名为 MyInvokeAndWait() 的新全局方法。此方法使用锁来确保一次只有一个线程可以调用InvokeAndWait()。然后使用IDE搜索所有InvokeAndWait()并替换为MyInvokeAndWait().

  2. 现在,在MyInvokeAndWait()中,确保当调用InvokeAndWait()时,一个原子变量threadId被设置为调用线程的id(注意InvokeAndWait() 的调用将阻塞调用线程)。当InvokeAndWait()完成后,threadId被清除。

  3. 这样,每当EDT访问数据时,都可以检查owner thread是否与threadId有相同的id。如果是这种情况,让 EDT 完成它的工作,否则抛出异常。

嗯...您不需要确保一次只有一个线程可以调用 InvokeAndWait()。您可以将所有调用线程 ID 添加到集合中,然后验证所有者线程 ID 是否在集合中。

你可以做的是拥有自己的 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();
                }
            }
        });
    }
}