Single Threaded Execution模式

所谓 Single Threaded Execution模式,意即“以一个线程执行”。就像独木桥同一时间内只允许一个人通行一样,该模式用于设置限制,以确保同一时间内只能让一个线程执行处理。

下面示例程序 不使用Single Threaded Execution模式:

该程序模拟的是三个人频繁地通过一个只允许一个人经过的门的情形当人们通过门的时候,统计人数便会递增。另外,程序还会记录通行者的“姓名与出生地”该程序使用到的类如表1-1所示。
名字 说明
Main 创建门,并让三个人不断地通过的类
Gate 表示门的类。它会在人们通过门时记录其姓名与出生地
UserThread 表示人的类。人们将不断地通过门

Main.java

1
2
3
4
5
6
7
8
9
    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();
}
}

Gage.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 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());
}
}
}

UserThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 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);
}
}
}

运行程序……出错了
日志

出错原因:在某个线程执行check方法时,其他线程不断执行pass方法,改写了name字段和address字段。线程改写共享的实例字段时并未考虑其他线程的操作。

** Single Threaded Execution模式登场 **

下面,将Gate类修改成线程安全的类,其他类则不需要修改:

Gage.java

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
 public class Gate {
private int counter = 0;//通过的人数
private String name = "Nobody";//通过人的姓名
private String address = "Nowhere";//通过人的地址

//使人通过该门
public synchronized void pass(String name,String address){
counter++;
this.name=name;
this.address=address;
check();
}

@Override
public synchronized String toString() {
return "NO."+counter+",:"+name+","+address;
}
//验证通过时记录的姓名和地址是否是同一个人
//如果不是,则输出BROKEN
public void check() {
if(!address.contains(name)){
System.out.println("----BROKEN----"+toString());
}else{
System.out.println(toString());
}
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
NO.52446,:c,ccc
NO.52447,:c,ccc
NO.52448,:c,ccc
NO.52449,:c,ccc
NO.52450,:c,ccc
NO.52451,:c,ccc
NO.52452,:c,ccc
NO.52453,:c,ccc
NO.52454,:c,ccc
NO.52455,:c,ccc
NO.52456,:c,ccc

无论等多久都没有BROKEN

为什么在pass方法和toString方法上加synchronized就不在显示BROKEN了呢?
正如前面所说,之所以显示BROKEN是有限pass方法被多个线程穿插执行。加synchronized,能保证只有一个线程执行它。
而check方法,只有pass方法能调用它,所以其他类无法调用check方法,所以,不需要设置成synchronized(设置也没有关系,但是可能会影响程序性能)。toString 加上synchronized的原因是由于,在其他线程调用toString方法是,可能存在在获取name和adress是不一致的情况。

synchronized 方法同时只有一个线程能进行操作,从多线程的角度看,是“原子的操作”,本意是不可分割的操作。

在java中,基本数据类型(primitive)如int, char, byte等都是原子性操作的,但是long和double则不一定,所以我们想把long和double这类非原子性类型按照原子性方式操作,需要加上关键字volatile,这样就可以了。