Single Threaded Execution Pattern是多线程中最为简单的一种模式,其用来限制同时只能让一个线程运行,用于多个线程共享资源(sharedResource)的情况。
文章中举了一个例子来说明该模式的使用。程序模拟3个人频繁经过只能同时通过一个人的门,当人通过门时,程序会在计数器中递增通过人数,并记录通过人员的姓名和地址。
程序中使用的类如下表所示:
名称 | 解说 |
Main | 创建一个门,并操作三个人不断的穿越门的类 |
Gate | 表示门的类,当人经过时会记录人的姓名和地址 |
UserThread | 表示人的类,只负责处理不断的在门间穿越 |
首先是不使用Single Threaded Execution Pattern的情况:
Main类:
public class Main {
public static void main(String[] args) {
System.out.println("Testing Gate, hit CTRL+C to exit.");
Gate gate = new Gate();
new UserThread(gate, "Alice", "Alaska").start();
new UserThread(gate, "Bobby", "Brazil").start();
new UserThread(gate, "Chris", "Canada").start();
}
}
非线程安全(Thread-safe)的Gate类:
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public String toString() {
return "No." + counter + ": " + name + ", " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("***** BROKEN ***** " + toString());
}
}
}
其中check()方法是来检测门的状态的,如果姓名和地址的首字母不一致,则表示记录有问题,打印出BROKEN。
UserThread类:
package com.hik.test;
public class UserThread extends Thread {
private final Gate gate;
private final String myname;
private final String myaddress;
public UserThread(Gate gate, String myname, String myaddress) {
this.gate = gate;
this.myname = myname;
this.myaddress = myaddress;
}
public void run() {
System.out.println(myname + " BEGIN");
while (true) {
gate.pass(myname, myaddress);
}
}
}
运行结果:
**** BROKEN ***** No.654810203: Alice, Alaska
***** BROKEN ***** No.654810878: Chris, Canada
***** BROKEN ***** No.654811529: Chris, Alaska
***** BROKEN ***** No.654802550: Bobby, Canada
***** BROKEN ***** No.654812495: Alice, Brazil
***** BROKEN ***** No.654811529: Chris, Alaska
***** BROKEN ***** No.654813485: Chris, Canada
***** BROKEN ***** No.654814227: Alice, Alaska
***** BROKEN ***** No.654814972: Alice, Alaska
***** BROKEN ***** No.654815613: Alice, Alaska
可以看到,运算结果出现了错误,一种情况是即使姓名和地址的首字母一致,但还是打印出了BROKEN,另一种情况就是姓名和地址的首字母不一致的情况。
为什么会出现这种情况?
因为pass方法可以同时被多个线程调用,多个线程调用pass方法时,里面的语句可能是交错运行的,这就有可能出现姓名和地址的首字母不一致的情况。情况有可能如下图所示:
因为,所有线程都是调用同一个pass方法,它们之间是共享的关系,为了避免上述情况的发生,就必须保证同一时间只能有一个线程在访问pass方法。因此,可以将Gate类改为线程安全的类:
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public synchronized void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public synchronized String toString() {
return "No." + counter + ": " + name + ", " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("***** BROKEN ***** " + toString());
}
}
}
先来看下运行结果:
Testing Gate, hit CTRL+C to exit.
Alice BEGIN
Bobby BEGIN
Chris BEGIN
(无论等多久,都不会打印出BROKEN)
运行结果正确。通过比较两个Gate类,可以发现,线程安全的Gate类只改了一点点,就是在pass()方法和toString()方法前面加上了synchronized关键字,因为synchronized方法可以保证同一时间只有一个线程可以执行它。
这样我们就完成了一个Single Threaded Execution Pattern。当然这过程中我们还需要注意几点:
- 死锁