什么是Thread-Per-Message Pattern?
设想一个场景,妻子在忙着淘宝,对丈夫说,“亲爱的,能不能帮我倒一下垃圾。”,然后老公去倒垃圾,老婆继续逛淘宝,这就是Thread-Per-Message Pattern,拜托别人,“这件事就交给你了”以后再回来做自己的事。
对每个命令或者请求,分配一个线程,由这个线程执行工作。
现在有这样的需求,启动三个线程,分别输出10个A,20个B,30个C,允许字母交叉,首先看代码:
首先写一个Helper类,负责对字母进行输出:
package threadPerMessage;
public class Helper {
public void handle(int count, char c) {
System.out.println(" handle(" + count + ", " + c + ") BEGIN");
for (int i = 0; i < count; i++) {
slowly();
System.out.print(c);
}
System.out.println("");
System.out.println(" handle(" + count + ", " + c + ") END");
}
private void slowly() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
然后写一个Host类,作为Helper的宿主类,对外提供helper的方法:
package threadPerMessage;
public class Host {
private final Helper helper = new Helper();
public void request(final int count, final char c) {
System.out.println(" request(" + count + ", " + c + ") BEGIN");
new Thread() {
public void run() {
helper.handle(count, c);
}
}.start();
System.out.println(" request(" + count + ", " + c + ") END");
}
}
最后写一个测试类Test:
package threadPerMessage;
public class Test {
public static void main(String[] args) {
System.out.println("main BEGIN");
Host host = new Host();
host.request(10, 'A');
host.request(20, 'B');
host.request(30, 'C');
System.out.println("main END");
}
}
回过头来看代码,在Host类中我们发现了一个匿名内部类,建立了Thread子类的实例,并且启动线程:
new Thread(){
@Override
public void run(){
helper.handle(count,c);
}
}.start();
实际上是把类声明,建立实例,启动线程写在了一起。
匿名内部类:将类的声明和建立实例的操作写在一起,执行方法的时候才建立类文件。匿名类和一般类相同,都会在编译时产生出类文件。 当我们在匿名内部类中用到方法的参数或者局部变量时,必须将变量声明成final,如果不是final,会发生编译错误。
Thread-Per-Message Pattern的所有参与者
1、Client(委托人)参与者
Client参与者对Host参与者发出请求,Client不知道Host如何实现这个请求,但知道他会去做。
2、Host参与者
当Host收到Client发出的请求的时候,会建立新的线程启动它,这个新的线程,会使用Helper参与者,处理这个请求。
3、Helper(帮助者)参与者
Helper参与者会对Host参与者提供处理请求的功能,Host参与者所建立的线程,会使用Helper参与者。
什么时候使用这个模式?
1、提升响应度,降低延迟时间
使用这个模式,Host对Client的响应度会提高,延迟时间会下降,尤其当处理函数很花时间或者需要等待I/O的时候,效果很明显。
2、适合在操作顺序无所谓时使用
这个模式中,handle方法执行顺序不一定时request方法调用顺序。
3、不需要返回值的时候
这个模式中,request方法不会等到handle方法执行结束,也就是说request方法拿不到handle方法的返回值。
4、应用在服务器的制作
这个模式可以在客户端送达请求,主线程接收,其他线程处理该请求的情况下使用。
5、调用方法+启动线程→传送消息
通常调用出普通方法的时候,执行完方法里的操作,控制权才会回来,然而在这个模式里request时期待才做开始的触发器,但是不会等待到执行结束(传送异步消息)。
进阶说明:进程与线程
进程与线程之间的关系,会因为平台的差异,有极大的不同,但是我们一般可以说一个进程可以建立多个线程。
线程的内存时共享的。
线程和进程最大的差异在于内存是否能共享,通常进程的内存空间是各自独立的,进程不能擅自读取、改写其他进程的内存空间。因为进程内存空间的独立性,进程无须担心被其他进程破坏。
线程则是共享内存的,所以我们进程在一条线程的内存上写入数据,而其他线程来读取。这里说的共享相同的内存,在Java中就体现为共享相同的实例。
因为线程间的内存是共享的,因此线程之间的沟通可以很自然、简单地做到。然而一位内同一个实例可以有多个线程同时访问,所以需要正确地进行互斥共享。
线程间的context-switch较容易
进程和线程的另一个差异,在于contex-switch的繁重程度。
要切换进程的时候,进程需要将当前自己的状态(context信息)存储下来,并将下一个要开始执行的进程以前所保留的context数据读回来,这个信息切换操作(context switch)所需要花费一些时间。
切换执行线程的时候,线程也需要进行context-switch操作,然而,线程所管理的context信息比进程要少,一般而言线程的context-swicth要比进程的context-switch快得多。所以需要紧密进行多项相关工作得时候,线程通常比进程来得实用。
PS、Java的内存模型中,将内存分为主存储器和工作内存两种,能够让线程共享的,只有主存储器部分。