Balking模式

Balk在这里的意思大概是“停止并返回”,棒球中的投手犯规就是这个词。在充分理解前面的Guarded Suspension模式后,学习Balking模式应该是相当容易的。我们可以看一下在某些场景下Guarded Suspension模式可能存在的缺点。Guarded Suspension模式中如果守护条件得不到满足,那么该线程将一直处于阻塞状态,在很多情况下,如上一篇文章中提到的多线程队列,而在另外的情况下,我们其实可以直接返回,而不必使线程阻塞,从而提高性能,这就是Balking模式。

比如在文本的自动保存中,我们可能会用到一个单独的定时启动的线程判断文本是否修改,如果没有修改(不满足守护条件),那么线程可以直接返回,而不必阻塞。由于思路只是在Guarded Suspension的基础上稍作改变,这里就不给出代码示例了。

Producer-Consumer模式

接下来是经典的生产者-消费者模型。生产者负责生产“商品”,消费者负责购买“商品”。当生产者和消费者都由同一个线程控制时,这似乎没有什么难的。但如果还原现实,生产者和消费者分别由单独的线程控制,甚至有多个生产者和消费者(多个线程),那么就需要一个桥梁来连接生产者和消费者。这个中介桥梁大多数情况是有限的,只能放置有限个商品。生产者生产的商品需要放到桥梁上,当桥梁满时,需要等待;而消费者需要通过桥梁购买商品,当桥梁上没有商品时,也需要等待。Producer-Consumer模式就是为了解决这种线程之间处理速度有差异的情况。

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
public class Bridge {
private Item[] buffer; //放置商品的容器
private int tail; //下次放置商品的位置
private int head; //下次拿走商品的位置
private int count; //buffer中商品的数目

public Brdige(int total) {
this.buffer = new Item[total];
this.tail = 0;
this.head = 0;
this.count = 0;
}

public synchronized void put(Item item) throws InterruptedException {
while (count >= buffer.length) {
wait();
}
buffer[tail] = item;
tail = (tail + 1) % count;
count++;
notifyAll();
}

public synchronized Item get() throws InterruptedException {
while (count <= 0) {
wait();
}
Item item = buffer[head];
head = (head + 1) % count;
count--;
notifyAll();
return item;
}
}

上述Brdige模拟的是生产者消费者沟通的桥梁,put由生产者调用,将商品放在桥梁里;get由消费者调用,获取放置在桥梁里的商品。如果您已经完全理解了之前的Guarded Suspension模式,那么也许会觉得get和put的逻辑无比简单。首先,get和put都必须被synchronized关键字修饰,即获得锁的线程才可以调用,避免冲突。其次,get和put方法的内部使用了Guarded Suspension模式。如果不满足守护条件,即生产者遇到buffer满的情况,或是消费者遇到buffer为0的情况,线程都会被阻塞,直到另外一方调用相应方法,进而notify。由此保证了当生产者和消费者不同步时,程序也能正常执行。

对InterruptedException异常的理解

该异常会由以下三个方法抛出:

  • java.lang.Object.wait
  • java.lang.Thread.join
  • java.lang.Thread.sleep

InterruptedException表示该方法可能会花费时间,但可以取消

  • 花费时间。wait方法调用后线程会进入等待队列,直到notify’或notifyAll,sleep方法调用后线程会被暂停一定的时间,join方法调用后,线程会等待指定线程终止。
  • 可以取消。对由于执行上述三个方法而暂停的线程t,可以由别的线程执行t.interrupt()来唤醒线程t,控制权会被传递给catch到InterruptedException的语句块。值得注意的是,wait方法的情况下,我们需要注意锁的情况,只有线程t重新获得锁,它才会抛出异常。

Thread-Per-Message模式

该模式直译就是每个消息一个线程,这里的消息主要指一些非常耗时的操作如IO等,意思就是把耗时的操作交给另外一个线程来执行,进而提高程序的相应性。这体现的是多线程程序的一个很重要的作用,就是提高程序的相应性,降低延迟。在Thread-Per-Message模式中,每一个message(耗时的操作)到来时,都会创建一个新的线程去执行,而主线程会立即返回,执行其他的操作。我们用Client来表示类似主线程的角色,由它来发起操作;用Host来表示负责创建线程的类;Helper类表示具体执行耗时操作的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Helper {
public void handle() {
... //耗时的操作,如IO等
}
}

public class Host {
private final Helper helper = new Helper();
public void request() {
new Thread() {
public void run() {
helper.handle();
}
}.start();
}
}

public class Client {
Host host = new Host();
host.request();
host.request();
...
}

Host的实现使用了匿名内部类,把类的声明、创建、方法的调用结合在了一起,具体语法不在这里赘述。

Thread-Per-Message的主要目的是提高程序相应性,降低延迟,但在使用之前,我们需要注意它的一些特点,避免错误使用。

  • 操作顺序无法保证。handle的执行并不一定会按照request的调用顺序,异常依赖于顺序的场景该模式是不适用的
  • 没有返回值。handle方法是无法返回值的,因为request方法不会等待handle方法执行完就会返回。如果需要handle的返回值,可以考虑之后会介绍到的Future模式