Single Thread Execution模式是指在同一时刻只能有一个线程去访问共享资源,就像独木桥一样每次只允许一人通行,简单来说,Single Thread Execution就是采用排他式的操作保证在同一时刻只能有一个线程访问共享资源。
1 场景举例:安检
在进入登机口之前必须经过安全检查,安检口类似于独木桥,每次只能通过一个人,工作人员除了检查你的登机牌以外,还要联网检查身份证信息以及是否携带危险物品
1.1 非线程安全实现
public class FlightSecurity {
/**
* 统计经过的人数
*/
private int count;
/**
* 登机牌
*/
private String boardingPass = null;
/**
* 身份证
*/
private String idCard = null;
public void pass(String boardingPass, String idCard) {
this.boardingPass = boardingPass;
this.idCard = idCard;
this.count++;
check();
}
/**
* 简单模拟一下安检的逻辑:当登机牌和身份证首字母不相同时则表示检查不通过
*/
private void check() {
if (this.boardingPass.charAt(0) != this.idCard.charAt(0)) {
throw new RuntimeException("====Exception====" + toString());
}
}
@Override
public String toString() {
return "The " + count + " passengers,boardingPass [" + boardingPass + "],idCard [" + idCard + "]";
}
}
测试:
public class FlightSecurityTest {
/**
* 定义登机人
*/
static class Passengers extends Thread {
private final FlightSecurity flightSecurity;
private final String idCard;
private final String boardingPass;
Passengers(FlightSecurity flightSecurity, String idCard, String boardingPass) {
this.flightSecurity = flightSecurity;
this.idCard = idCard;
this.boardingPass = boardingPass;
}
@Override
public void run() {
// 模拟这个乘客不断的安检
while (true) {
flightSecurity.pass(boardingPass, idCard);
}
}
}
public static void main(String[] args)
{
final FlightSecurity flightSecurity = new FlightSecurity();
// 定义三个旅客,身份证和登机牌首字母均相同
new FlightSecurityTest.Passengers(flightSecurity, "A123456", "AF123456").start();
new FlightSecurityTest.Passengers(flightSecurity, "B123456", "BF123456").start();
new FlightSecurityTest.Passengers(flightSecurity, "C123456", "CF123456").start();
}
}
运行发现,会出现两种不通过的情况
1.====Exception====The 658 passengers,boardingPass [CF123456],idCard [B123456]
2. ====Exception====The 642 passengers,boardingPass [BF123456],idCard [B123456]
按理说应该都通过啊,原因就是发生了数据不一致
1.2 数据不一致原因分析
1.首字母相同却未通过检查
1. 线程A调用pass方法,传入”A123456”“AF123456”并且对idCard赋值成功,由于CPU调度器时间片的轮转,CPU的执行权归B线程所有。
2. 线程B调用pass方法,传入”B123456”“BF123456”并且对idCard赋值成功,覆盖A线程赋值的idCard
3. 线程A重新获得CPU的执行权,将boardingPass赋予AF123456,而idCrad已经被线程B赋值为B123456,因此check无法通过。
4. 在输出toString之前,B线程成功将boardingPass覆盖为BF123456。
- 为何出现首字母不相同的情况
- 线程A调用pass方法,传入”A123456”“AF123456”并且对idCard赋值成功,由于CPU调度器时间片的轮转,CPU的执行权归B线程所有。
- 线程B调用pass方法,传入”B123456”“BF123456”并且对idCard赋值成功,覆盖A线程赋值的idCard。
- 线程A重新获得CPU的执行权,将boardingPass赋予AF123456,而idCrad已经被线程B赋值为B123456,因此check无法通过。
- 线程A检查不通过,输出idCard=”A123456”和boardingPass=”BF123456”。
1.3 解决数据不一致问题(改为线程安全)
就是加锁,对共享资源加锁:
public synchronized void pass(String boardingPass, String idCard) {
this.boardingPass = boardingPass;
this.idCard = idCard;
this.count++;
check();
}