《Java多线程设计模式》读书笔记3
目录:
1 Thread-Per-Message Pattern
2 Worker Thread Pattern
3 Future Pattern
===================Thread-Per-Message Pattern===============
Thread per message,每个消息一个线程。Message 在这里可以看作是“命令”或“请求”的意思。对每个命令或请求,分配一个线程,有这个线程执行工作,这就是Thread-Per-Message Pattern。
Thread-Per-Message Pattern的适用场合:
1. 适合在操作顺序无所谓时使用。
2. 在不需要返回值的时候。
3. 可以应用在服务器的制作上,提升响应性,降低延迟时间。
进程和线程
1 进程和线程最大的差异在于内存能否共享。
通常每个进程所拥有的内存空间是各自独立的。进程不能擅自读取,改写其他进程的内存空间。因为进程的内存空间是相互独立的,所以进程无需担心被其他进程破坏的危险。而线程则是共享内存的。
2 进程和线程另一个差异,在于context-switch的频繁程度。
进程切换需要存储和保留的信息比较多,所以切换需要花费一些时间。然而线程需要管理的context信息比进程要少得多,所以一般而言线程context-switch比进程的context-switch要快的多。
===================Worker Thread Pattern===============
线程池机制 主要解决每次请求都要建立新线程的成本。事先启动用来执行工作的线程(工人线程)备用。并使用Producer-Consumer Pattern,将表示工作内容的实例传递给工人线程。这么一来,工人线程会负责执行工作,就不需要一直启动新的线程了。
实现:
工作区(核心)
public class Channel {
//允许最多请求数
private static final int MAX_REQUEST = 100;
//请求容器
private final Request[] requestQueue;
private int tail; // 下一个putRequest的地方
private int head; // 下一个takeRequest的地方
private int count; // Request的数量
//工作线程组
private final WorkerThread[] threadPool;
public Channel(int threads) {
//初始请求容器
this.requestQueue = new Request[MAX_REQUEST];
this.head = 0;
this.tail = 0;
this.count = 0;
//初始化工作线程组
threadPool = new WorkerThread[threads];
for (int i = 0; i < threadPool.length; i++) {
threadPool[i] = new WorkerThread("Worker-" + i, this);
}
}
//工作线程开始工作
public void startWorkers() {
for (int i = 0; i < threadPool.length; i++) {
threadPool[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;
}
}
请求对象
public class Request {
private final String name; // 委托者
private final int number; // 请求编号
private static final Random random = new Random();
public Request(String name, int number) {
this.name = name;
this.number = number;
}
public void execute() {
System.out.println(Thread.currentThread().getName() + " executes " + this);
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
}
}
public String toString() {
return "[ Request from " + name + " No." + number + " ]";
}
}
工作线程
public class WorkerThread extends Thread {
private final Channel channel;
public WorkerThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
public void run() {
while (true) {
Request request = channel.takeRequest();
request.execute();
}
}
}
放入请求线程
public class ClientThread extends Thread {
private final Channel channel;
private static final Random random = new Random();
public ClientThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
public void run() {
try {
for (int i = 0; true; i++) {
Request request = new Request(getName(), i);
channel.putRequest(request);
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
}
}
}
测试类
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();
}
}
===================Future Pattern===============
Future是“未来”,“期货”的意思。假设有一个执行起来需要花一些时间的方法,我们就不要等待执行结果出来了,而获取一张替的“提货单”。因为获取提货单不需要花时间,这时这个“提货单”就是Future参与者。获取Future参与者的线程,会在事后再去获取执行结果。就好像那提货单去领取蛋糕一样,如果已经有执行结果了,就可以马上拿到数据。如果执行结果还没好,则继续等待到执行结果出现为止。
Thread-Per-Message Pattern是将花费时间的工作交给别的线程,以提高程序的响应性。不过,如果我们需要得到别的线程所处理的结果时,就行不通了。若同步执行需要花一些时间的操作,会使程序响应性降低。但是,如果异步的开始执行,却无法在第一时间得知结果。这种时候就要使用Future Pattern。首先我们建立一个与处理结果具有相同接口(API)的Future参与者。接着,在开始处理时,先把Future参与者当作返回值返回。直到其他线程处理完以后,才将真正的结果设置给Future参与者。Client参与者可以通过Future参与者得到处理的结果。使用这个Pattern,可使响应性不降低,并得到想要的处理结果。