多线程编程中的任务处理与消息传递技术
发布时间: 2025-08-17 02:17:24 阅读量: 1 订阅数: 3 

### 多线程编程中的任务处理与消息传递技术
#### 1. 线程池与资源管理
在资源敏感型应用中,当请求率增加时,闲置的线程会被新线程替代。同时,还可以将其他资源(如可重复使用的图形对象集)与每个工作线程关联起来,从而将资源池与线程池相结合。
在任务取消方面,需要区分任务的取消和执行该任务的工作线程的取消。具体方法如下:
- 当中断发生时,如果工作队列不为空或有新任务到来,允许当前工作线程死亡,并在必要时用新的工作线程替代。
- 在工作线程类中提供一个关闭方法,使现有工作线程死亡,并且不再创建新的工作线程。
此外,如果在任务交接期间主机线程被取消,可能需要触发某种错误处理。虽然在 `PlainWorkerPool` 中默默吞掉 `InterruptedException` 而不排队任务符合单向消息传递框架的最低要求,但大多数应用需要采取其他补救措施。
#### 2. 事件队列
许多基于事件的框架(如 `java.awt` 和 `javax.swing` 包中支持的框架)依赖于这样的设计:恰好有一个工作线程操作一个无界队列。该队列保存 `EventObject` 实例,这些实例通常需要分发到应用程序定义的监听器对象(与自调度的 `Runnable` 对象不同)。监听器通常与最初生成事件的对象相同。
使用单线程操作单个事件队列与一般的工作线程设计相比,简化了使用,但也带来了一些事件框架特有的限制:
- **利用队列的排序属性进行优化**:例如,可以使用自动事件过滤技术,在重复的重绘事件到达队列前端并被工作线程处理之前,将其移除或合并。
- **线程限制**:可以要求对某些对象进行操作的所有方法只能通过向队列发出事件来调用,从而最终由单个工作线程执行。这实现了这些对象的一种线程限制形式,如果严格遵守,可消除这些对象操作中的动态锁定需求,提高性能,还能降低不需要构建线程的应用程序的复杂性。这也是 Swing 单线程规则的基础:除少数例外情况外,所有对 Swing 对象的操作都必须由事件处理线程执行。在 AWT 中,遵循此规则也是个好主意。
- **事件启用时机**:事件在其处理程序完全构造好并准备好处理事件之前不应启用。这在其他基于线程的设计中也适用,但在这里更容易出错,因为在构造函数中注册事件处理程序或监听器不像构造线程那样明显会过早启用并发执行。
- **避免阻塞操作**:事件框架的用户绝不能调度那些只能通过处理未来事件才能解除阻塞的操作。在大多数事件框架中实现模态对话框时会遇到这个问题,需要临时解决方案。可以通过为交互式组件设置禁用状态,直到收到特定的重新启用事件,避免阻塞事件队列,同时防止触发不期望的操作。
- **保持响应性**:为了保持事件框架的响应性,操作不应阻塞,也不应执行耗时的操作。
这些设计选择使事件框架比每个事件一个线程的设计具有更好的性能,并且使不使用线程的开发人员更容易编程。然而,这些使用限制在构建其他线程的程序中影响更大。例如,由于单线程规则,即使是对 GUI 组件的最小操作(如更改标签中的文本)也必须通过发出可运行的事件对象来执行,这些对象封装了要由事件处理线程执行的操作。在 Swing 和 AWT 应用程序中,可以使用 `javax.swing.SwingUtilities.invokeLater` 和 `java.awt.EventQueue.invokeLater` 方法在事件处理线程中执行与显示相关的命令。
#### 3. 定时器
在工作线程设计中,`Runnable` 任务可能会排队而不运行,这在某些应用中是需要解决的问题,但在需要延迟操作时有时会成为一个特性。使用工作线程可以提高延迟和周期性操作的效率并简化其使用,这些操作在特定时间、经过特定延迟或定期触发(例如每天中午)。标准化的定时器工具可以自动进行复杂的时间计算,并通过重用工作线程避免过多的线程创建。但如果一个工作线程阻塞或处理一个任务花费很长时间,其他任务的触发可能会比使用底层 JVM 创建和调度单独线程时延迟更长。
以下是一个基于优先级队列的 `TimerDaemon` 类示例:
```java
class TimerDaemon { // Fragments
static class TimerTask implements Comparable { // ...
final Runnable command;
final long execTime; // time to run at
public int compareTo(Object x) {
long otherExecTime = ((TimerTask)(x)).execTime;
return (execTime < otherExecTime) ? -1 :
(execTime == otherExecTime)? 0 : 1;
}
}
// a heap or list with methods that preserve
// ordering with respect to TimerTask.compareTo
static class PriorityQueue {
void put(TimerTask t);
TimerTask least();
void removeLeast();
boolean isEmpty();
}
protected final PriorityQueue pq = new PriorityQueue();
public synchronized void executeAfterDelay(Runnable r, long t){
pq.put(new TimerTask(r, t + System.currentTimeMillis()));
notifyAll();
}
public synchronized void executeAt(Runnable r, Date time) {
pq.put(new TimerTask(r, time.getTime()));
notifyAll();
}
// wait for and then return next task to run
protected synchronized Runnable take()
throws InterruptedException {
for (;;) {
while (pq.isEmpty())
wait();
TimerTask t = pq.least();
long now = System.currentTimeMillis();
long waitTime = now - t.execTime;
if (waitTime <= 0) {
pq.removeLeast();
return t.command;
}
else
wait(waitTime);
}
}
public TimerDaemon() { activate(); } // only one
void activate() {
// same as PlainWorkerThread except using above take method
}
}
```
该类可以扩展以处理周期性任务,但需要处理周期性调度操作几乎从不完全周期性的问题。主要有两种选择:忽略延迟并按时钟时间重新调度,或者忽略时钟并在当前执行开始后的固定延迟后重新调度下一次执行。对于多媒体同步,通常需要更复杂的方案。定时器守护程序还可以支持取消延迟或周期性操作的方法,一种方法是让 `executeAt` 和其他调度方法接受或返回支持取消方法的 `TimerTask`,该方法设置工作线程会遵循的状态标志。
#### 4. 轮询和事件驱动的 IO
大多数工作线程设计依赖于阻塞通道,工作线程在其中等待传入命令运行。但在某些情况下,乐观式重试循环提供了更好的解决方案,主要涉及执行来自通过 IO 流接收的消息的命令。
在高负载的 IO 绑定系统中,实现低延迟和高吞吐量是一个挑战。创建执行基于 IO 任务的线程所花费的时间会增加延迟,但大多数运行时系统经过调优,一旦线程创建,它们对 IO 流上的新
0
0
相关推荐










