我们可以使用 Thread-Per-Message 模式将 "发出工作请求的线程" 与 "执行工作请求的线程" 分开, 来提高程序的响应速度, 但是如果每次发出工作请求时都要创建执行工作的线程就太浪费了性能了, 这里介绍 Worker-Thread 模式, 可以事先启动执行工作的线程, 然后使用 Producer-Consumer 模式将表示工作内容的实例传递给工作线程, 这就是Worker-Thread模式
Worker 的意思是工人劳动者, 在 Worker-Thread 模式中, 工人线程(WorkerTHread)会逐个取回工作并进行处理, 当所有工作完成后, 工人线程会等待新的工作到来, 该模式也被称为 Background Thread(背景线程模式), 如果从 "保存多个工人下线程的场所" 这一点来看, 这种模式也被称为 Thread Pool 模式
类名 | 说明 |
Main | 测试程序类 |
ClientThread | 表示发出工作请求的线程的类 |
Request | 表示工作请求的类 |
Channel | 接收工作请求并将工作请求交给工人线程的类 |
WorkerThread | 表示工人线程的类 |
Main 类会创建一个雇佣了五个工人线程的 Channel 的实例, 并将其共享给三个 ClientThread 的实例(Alice, Bobby, Chris)
public class Main {
public static void main(String[] args) {
Channel channel = new Channel(5);
channel.startWorkers();
new ClientThread("alice", channel).start();
new ClientThread("bobby", channel).start();
new ClientThread("chris", channel).start();
}
}
ClientThread 类的是发送工作请求类, 会创建 Request 实例, 并将该实例传递给 Channel 类的 putRequest() 方法
public class ClientThread extends Thread {
private final Channel channel;
private static final Random R = new Random();
public ClientThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
@Override
public void run() {
try {
for (int i = 0; true; i++) {
Request request = new Request(getName(), i);
channel.putRequest(request);
Thread.sleep(R.nextInt(1000));
}
} catch (InterruptedException e) {
//
}
}
}
Request 类是表示工作请求的类
public class Request {
private final String name;
private final int num;
private static final Random R = new Random();
public Request(String name, int num) {
this.name = name;
this.num = num;
}
public void execute() {
System.out.println(Thread.currentThread().getName() + " executes " + this);
try {
Thread.sleep(R.nextInt(1000));
} catch (InterruptedException e) {
//
}
}
@Override
public String toString() {
return "Request{" + "name='" + name + '\'' + ", num=" + num + '}';
}
}
Channel 是负责传递工作请求以及保存工人线程的类, 为了传递请求, 在 Channel 类中定义 RequestQueue 字段, 该字段扮演保存队列的角色, putRequest() 方法用于将请求加入到队列中, takeRequest() 方法用于取出队列中的请求, 这里使用到了 Producer-Consumer 模式, 另外为了实现 putRequest() 方法, 这里还是用到了 Guarded Suspension 模式
Channel 类中定义一个用于保存工人线程的 threadPool 字段, threadPool 是 WorkerThrea 的数组, Channel 类的构造函数会初始化 threadPool 字段并创建 WorkerThread 的线程实例
public class Channel {
private static final int MAX_REQUEST = 100;
private final Request[] requestQueue;
private int tail;
private int head;
private int count;
private final WorkerThread[] workerThreadPool;
public Channel(int threadCount) {
this.requestQueue = new Request[MAX_REQUEST];
this.head = 0;
this.tail = 0;
this.count = 0;
workerThreadPool = new WorkerThread[threadCount];
// 初始化工作线程
for (int i = 0; i < workerThreadPool.length; i++) {
workerThreadPool[i] = new WorkerThread(" Worker-" + i, this);
}
}
public void startWorkers() {
for (int i = 0; i < workerThreadPool.length; i++) {
workerThreadPool[i].start();
}
}
public synchronized void putRequest(Request request) {
while (count >= requestQueue.length) {
try {
wait();
} catch (InterruptedException e) {
//
}
}
requestQueue[tail] = request;
tail = (tail + 1) % requestQueue.length;
count++;
// 唤醒工作线程
notifyAll();
}
public synchronized Request takeRequest() {
while (count <= 0) {
try {
wait();
} catch (InterruptedException e) {
//
}
}
Request request = requestQueue[head];
head = (head + 1) % requestQueue.length;
count--;
// 唤醒客户端生产
notifyAll();
return request;
}
}
WorkerThread 是表示工人线程的类, 工人线程一旦启动后就会一直工作, 会反复执行 "获取一个新的 Request 的实例, 然后调用它的 execute() 方法进行处理
public class WorkerThread extends Thread {
private final Channel channel;
public WorkerThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
@Override
public void run() {
while (true) {
Request request = channel.takeRequest();
request.execute();
}
}
}
从图中可以看出, Worker-0, 1, 2, 3, 4 这五个 WorkerThread 线程正在处理来自于 alice, bobby, chris这三个 ClientThread 的请求, 发送请求的 ClientThread 与处理请求的 WorkerThread 之间没有固定的对应关系, 工人线程不不在意是谁发送的请求, 它只处理接收到的请求
Worker-4 executes Request{name='alice', num=0}
Worker-0 executes Request{name='bobby', num=0}
Worker-3 executes Request{name='chris', num=0}
Worker-1 executes Request{name='alice', num=1}
Worker-0 executes Request{name='alice', num=2}
Worker-2 executes Request{name='chris', num=1}
Worker-0 executes Request{name='bobby', num=1}
Worker-1 executes Request{name='alice', num=3}
Worker-4 executes Request{name='chris', num=2}
Worker-3 executes Request{name='bobby', num=2}
Worker-0 executes Request{name='bobby', num=3}
Worker-2 executes Request{name='bobby', num=4}
Worker-0 executes Request{name='alice', num=4}
Worker-1 executes Request{name='bobby', num=5}
Worker-3 executes Request{name='chris', num=3}
Client 创建表示工作请求的 Request 角色并将该角色传递给 Channel, 在示例程序中, 有 ClientThread 类扮演该角色
Channel 接收来自于 Cilent 的 Request 请求, 并将其传递给 Worker 角色
Worker 从 Channel 中获取 Request 角色, 并进行工作, 当一项工作完成后, 他会继续去获取另外的 Request
Request 表示工作实例, 保存了进行工作所必须的信息, 字段声明为final, 是线程安全的
java.util.concurrent.ThreadPoolExecutor 类是管理工人线程的类, ThreadPoolExecutor 可以轻松的实现 Worker-Thread 模式, 这里使用 java.util.concurrent.Exectors 类中的静态方法改造上面的示例程序
此时已经不在需要 WorkerThread 类, 因为已经使用juc包下的线程池替代了, 也不需要 Channel 类了, 因为线程池内部维护着一个 BlockingQueue (阻塞队列)
Main 类使用 Executors.newFixedThreadPool 方法创建了一个线程池, 该线程池保存了指定数量的线程, 是一个 ExecutorService 对象, 改造后 Main 类如下
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
new ClientThread("alice", executorService).start();
new ClientThread("bobby", executorService).start();
new ClientThread("chris", executorService).start();
Thread.sleep(5000);
} catch (InterruptedException e) {
//
} finally {
executorService.shutdown();
}
}
}
public class ClientThread extends Thread {
private final ExecutorService executorService;
private static final Random R = new Random();
public ClientThread(String name, ExecutorService executorService) {
super(name);
this.executorService = executorService;
}
@Override
public void run() {
try {
for (int i = 0; true; i++) {
Request request = new Request(getName(), i);
executorService.execute(request);
Thread.sleep(R.nextInt(1000));
}
} catch (InterruptedException e) {
//
}
}
}
为了能够让 ExecutorService 类使用 Request 类, 下面的程序必须实现 Runnable 接口, 在 run() 方法中做实际的处理
public class Request implements Runnable{
private final String name;
private final int num;
private static final Random R = new Random();
public Request(String name, int num) {
this.name = name;
this.num = num;
}
@Override
public String toString() {
return "Request{" + "name='" + name + '\'' + ", num=" + num + '}';
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " executes " + this);
try {
Thread.sleep(R.nextInt(1000));
} catch (InterruptedException e) {
//
}
}
}