关于线程分离的一点整理

Event dispatch thread EDT事件指派线程


  • !!!注意:线程分离(thread detached)和事件指派线程(event dispatch thread)并不是一个东西,线程分离是一个更为广域的概念,应用的场景也比事件指派要广;
线程的分离/可结合的 detached/joinable

线程的分离状态决定线程的终止模式。
分离:运行结束,线程即终止;
可结合的:在pthread_join()获得结合的另一个线程的中止状态才能释放此线程的资源。
pthread_join():暂停(阻塞)此线程(即自身)的运行,直到目标线程终止。

线程分离的原因:

串行执行任务(单线程)时,当遇到耗时较长的任务,线程将等待很长时间(进行任务),此时线程类似于进入阻塞状态,特别是对于UI操作,用户在这段时间无法对UI进行任何操作,故为了避免这种情况,线程中引入了线程并发(也就是将主线程和子线程分离)。


Java中的EDT线程

http://www.oracle.com/technetwork/articles/javase/swingworker-137249.html
Java的线程大致可分为三类:

  1. 起始线程(initial thread)
  2. EDT
  3. 多个工作线程(work threads)

main运行产生了起始线程,对于有GUI的java程序而言,initial thread在产生GUI后基本结束了自己的使命。
Swing应用中包含一个单独的EDT用于UI相关的操作。EDT线程绘制/更新GUI组件,并对用户交互做出反馈(通过event handler)。Java规定用户不能直接对(线程不安全)Swing部件直接进行调用,而是必须要通过EDT和EDT对应的event queue对Swing部件进行调用。

在Java中对于线程分离的规定:

在java中,特别是对于UI的Swing线程这种实际上是单线程处理的线程而言,涉及到UI相关的操作,将耗时较长的线程单独分离出去是非常必要的,并且是Java强制规定的。

Java的规定当中,如果Swing部件的方法被标明”thread safe”,那么这个线程可以从其它线程中被调用,但是其他所有Swing部件的方法都必须被放在Event dispatch thread中被调用,而非从主线程或其他工作线程中被调用。

总结起来一共2点:

  1. 耗时的任务不应在EDT中被运行
  2. Swing部件应在并且只在EDT被获取(即GUI相关的任务活动)。
    https://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html

Java GUI中与EDT交互的方法:

常用的方法有使用event-handling 方法,比如ActionListener.actionPerformed,或者可以使用invokeLater或者invokeAndWait方法。后两者的区别是later中主线程不会等待子线程而是继续运行,wait则会让主线程等待子线程运行完后才继续运行。

不过,由于Swing本身运行仍是串行运行在EDT上的,所以分离出的线程会进入一个线程队列中,等到线程拿到运行权,分离的线程才会运行。所以这就产生的一个新的问题发生点,分离出的子线程可能会在主线程运行很久以后才能得到运行。

多线程分离返回值或进行对UI操作的问题:

分离线程后,如果遇到主线程需要子线程的返回值再操作,或者需要子线程结束刷新UI的操作时,子线程的操作可能不能及时传达到主线程/UI中。对于这种情况,可以采取一些方法来处理:1)循环判断:主线程循环判断子线程是否完成操作,但这种方法占用很多内存,而且最坏的情况是由于主线程的循环判断子线程无法拿到运行权;2)回调:子线程调用主线程中的回调方法,从而进行继续的赋值或渲染操作;3)使用自带返回值的Callable接口,向主线程返回值;4)对于GUI相关操作,可以使用invokeAndWait方法;5)使用SwingWorker。

源码分析

EventQueue类:

  1. isDispatchThread
1
public static boolean isDispatchThread()

如果正在调用的线程是当前 AWT EventQueue 的指派线程,则返回 true。使用此调用确保给定的任务正在当前 AWT EventDispatchThread 上执行(或没有执行)。
返回:
如果给定的任务正在当前 AWT EventQueue 的指派线程上运行,则返回 true。

  1. invokeAndWait
1
2
3
public static void invokeAndWait(Runnable runnable)
throws InterruptedException,
InvocationTargetException

导致 runnable 的 run 方法在 the system EventQueue 的指派线程中被调用。在所有挂起事件被处理后才发生。在这发生之前调用被阻塞。如果从事件指派线程进行调用,则该方法将抛出 Error。
参数:
runnable - Runnable 对象,其 run 方法应该在 EventQueue 上同步执行
抛出:
InterruptedException - 如果任何线程中断了该线程
InvocationTargetException - 如果运行 runnable 时抛出一个 throwable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Causes <code>runnable</code> to have its <code>run</code>
* method called in the {@link #isDispatchThread dispatch thread} of
* {@link Toolkit#getSystemEventQueue the system EventQueue}.
* This will happen after all pending events are processed.
* The call blocks until this has happened. This method
* will throw an Error if called from the
*/
public static void invokeAndWait(Runnable runnable)
throws InterruptedException, InvocationTargetException
{
invokeAndWait(Toolkit.getDefaultToolkit(), runnable);
}
static void invokeAndWait(Object source, Runnable runnable)
throws InterruptedException, InvocationTargetException
{
if (EventQueue.isDispatchThread()) {
throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
}
class AWTInvocationLock {}
Object lock = new AWTInvocationLock();//加了一个AWT invocation锁
InvocationEvent event =
new InvocationEvent(source, runnable, lock, true);
synchronized (lock) {
Toolkit.getEventQueue().postEvent(event);
while (!event.isDispatched()) {
lock.wait();
}
}
Throwable eventThrowable = event.getThrowable();
if (eventThrowable != null) {
throw new InvocationTargetException(eventThrowable);
}
}
  1. invokelater

public static void invokeLater(Runnable runnable)
导致 runnable 的 run 方法在 the system EventQueue 的指派线程中被调用。
参数:
runnable - Runnable 对象,其 run 方法应该在 EventQueue 上同步执行

1
2
3
4
5
6
7
8
9
10
11
/**
* Causes <code>runnable</code> to have its <code>run</code>
* method called in the {@link #isDispatchThread dispatch thread} of
* {@link Toolkit#getSystemEventQueue the system EventQueue}.
* This will happen after all pending events are processed.
* 会在其他EDT中等待的进程结束运行后再运行
*/
public static void invokeLater(Runnable runnable) {
Toolkit.getEventQueue().postEvent(
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}

getEventQueue()获取了应用的event queue实例,
postEvent(): 令eventQueue中的线程呈现1.1关系。即每个队列中的线程都有着不同的ID和事件源(event source)(把runnable的事件放入EventQueue)
getDefaultToolkit(): 获取默认工具包
(关于java toolkit: http://blog.csdn.net/linhu007/article/details/18505423)
//InvocationEvent(): 创建一个源为DefaultToolkit的invacationEvent, 此源会在被指派时执行runnable的run方法(即执行invokelater中的被分离的子线程)

http://www.blogjava.net/vincent/archive/2008/08/24/223933.html
http://www.360doc.com/content/12/1022/01/820209_242886827.shtml
http://www.importnew.com/15761.html

EDT(事件派发线程) 此线程会调用事件队列(event queue)依次进行事件处理。InvokeLater和invokeAndWait都会把参数中的runnable事件放入事件队列尾,等待EDT派发。二者的区别是Wait会在完成事件后才返回(实现机制是给线程加锁),Later则是把事件放入就直接返回。

SwingWalker

https://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html
http://www.oracle.com/technetwork/articles/javase/swingworker-137249.html
SwingWalker的出现替代了前两个方法的工作,SwingWorker同样是创建了新的子线程,使用SwingWorker的doInBackground方法,把耗时的任务放到方法中,在方法完成后,SwingWorkder调用EDT中的done()方法,可用于返回结果值。

SwingWorker适用于需要在后台线程中执行长时间的运算工作时,对于运算完成时对UI的更新通知或者在运算过程中的更新。为了描述后台线程执行的具体工作,在继承抽象类SwingWorker的子类一定要实现doInBackground()方法。

在SwingWorker的生命循环中涉及到3个线程:

  1. 当前线程(current thread):
    execute()让SwingWorker开始运行工作在workder thread上; get()可以让current thread等待SwingWorker完成任务之后再继续进行;一般来说,current thread就是EDT本身。
  2. 工作线程(worker thread):
    doBackGround();使用firePropertyChange and getPropertyChangeSupport() methods. By default there are two bound properties available: state and progress.
  3. EDT:
    SwingWorkder触发done()并通知所有在此线程上的PropertyChangeListeners。

Before the doInBackground method is invoked on a worker thread, SwingWorker notifies any PropertyChangeListeners about the state property change to StateValue.STARTED. After the doInBackground method is finished the done method is executed. Then SwingWorker notifies any PropertyChangeListeners about the state property change to StateValue.DONE.
在SwingWorker开始和结束工作的时候,都会向Listeners发送通知。
SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.
SwingWorker只能被运行一次。
SwingWorker实现了Runnable借口,所以SwingWorker可以被提交到Executor中运行。

关于Executor (待更新)

顺便整理一个好用的在线markdown编辑器:https://pandao.github.io/editor.md/