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

时间:2015-04-23 08:53:38

标签: java c++ multithreading swing event-dispatch-thread

我有一个非常复杂的问题。在我目前的项目中,我有一个用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;
}

2 个答案:

答案 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()

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

嘿,路径尽头还有一些灯光。

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

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

  3. 这样,只要EDT访问数据,您就可以检查所有者线程是否与threadId具有相同的ID。如果是这种情况,请让EDT完成其工作,否则抛出异常。

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