第16章 Single Thread Execution设计模式(Java高并发编程详解:多线程与系统设计)

简单来说, Single Thread Execution就是采用排他式的操作保证在同一时刻只能有一个线程访问共享资源。

1.机场过安检

1.1非线程安全

先模拟一个非线程安全的安检口类,旅客(线程)分别手持登机牌和身份证接受工作人员的检查,示例代码如所示。

java 复制代码
public class FlightSecurity {
    private int count = 0;
    // 登机牌
    private String boardingPass = "null";
    // 身份证
    private String idCard = "null";
    
    public void pass(String boardingPass, String idCard) {
        this.boardingPass = boardingPass;
        this.idCard = idCard;
        this.count++;
        
    }
    
    private void check() {
        // 简单的测试,当登机牌和身份证首字母不相同时则表示检查不通过
        if ( boardingPass.charAt(0) != idCard.charAt(0)) 
            throw new RuntimeException("======Exception=====" + toString());
    }

    @Override
    public String toString() {
        return "FlightSecurity{" +
                "count=" + count +
                ", boardingPass='" + boardingPass + '\'' +
                ", idCard='" + idCard + '\'' +
                '}';
    }
}

Flight Security比较简单, 提供了一个pass方法, 将旅客的登机牌和身份证传递给pass方法, 在pass方法中调用check方法对旅客进行检查, 检查的逻辑也足够的简单, 只需要检测登机牌和身份证首字母是否相等(当然这样在现实中非常不合理,但是为了使测试简单我们约定这么做),我们看代码所示的测试.

java 复制代码
public class FlightSecurityTest {
    // 旅客线程
    static class Passengers extends Thread {
        // 机场安检类
        private final FlightSecurity flightSecurity;

        // 旅客的身份证
        private final String idCard;

        // 旅客的登机牌
        private final String boardingPass;

        // 构造旅客是传入身份证,登机牌以及机场安检类
        public 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 Passengers(flightSecurity, "A1234","AF1234").start();
        new Passengers(flightSecurity, "B1234", "BF1234").start();
        new Passengers(flightSecurity,"C1234", "CF1234").start();
    }
}

首字母相同检查不能通过和首字母不相同检查不能通过,为什么会出现这样的情况呢?首字母相同却不能通过?更加奇怪的是传入的参数明明全都是首字母相同的,为什么会出现首字母不相同的错误呢?

1.2 问题分析

(1)首字母相同却未通过检查

图所示的为首字母相同却无法通过安检的分析过程。

2

(2)为何出现首字母不相同的情况

明明传入的身份证和登机牌首字母都相同,可为何在运行的过程中会出现首字母不相同的情况,下面我们也通过图示的方式进行分析,如图所示。

1.3 线程安全

1.1节中出现的问题说到底就是数据同步的问题, 虽然线程传递给pass方法的两个参数能够百分之百地保证首字母相同, 可是在为FlightSecurity中的属性赋值的时候会出现多个线程交错的情况,结合我们在第一部分第4章的所讲内容可知,需要对共享资源增加同步保护,改进代码如下:

java 复制代码
    public synchronized void pass(String boardingPass, String idCard) {
        this.boardingPass = boardingPass;
        this.idCard = idCard;
        this.count++;
    }

何时适合使用single thread execution模式呢?答案如下。

  • 多线程访问资源的时候, 被synchronized同步的方法总是排他性的。
  • 多个线程对某个类的状态发生改变的时候, 比如Flight Security的登机牌以及身份证。

2.吃面问题

2.1吃面引起的死锁

虽然使用synchronized关键字可以保证single thread execution, 但是如果使用不得当则会导致死锁的情况发生,比如A手持刀等待B放下叉,而B手持叉等待A放下刀,示例代码如所示。

java 复制代码
public class EatNoodleThread extends Thread {

    private final String name;

    // 左手边的餐具
    private final Tableware leftTool;
    // 右手边的餐具
    private final Tableware rightTool;

    public EatNoodleThread(String name, Tableware leftTool, Tableware rightTool) {
        this.name = name;
        this.leftTool = leftTool;
        this.rightTool = rightTool;
    }

    @Override
    public void run() {
        while (true) {
            this.eat();
        }
    }

    // 吃面条的过程
    private void eat() {
        synchronized (leftTool) {
            System.out.println(name + " take up" + leftTool + "left");

            synchronized (rightTool) {
                System.out.println(name + "take up " + rightTool + "right");
            }

            System.out.println(name + " put down " + leftTool);
        }
    }

    public static void main(String[] args) {
        Tableware fork = new Tableware("fork");
        Tableware knife = new Tableware("knife");

        new EatNoodleThread("A", fork, knife).start();
        new EatNoodleThread("B", knife, fork).start();
    }
}

2.2 解决吃面引起的死锁问题

为了解决交叉锁的情况,我们需要将刀叉进行封装,使刀叉同属于一个类中,改进代码如所示

java 复制代码
public class EatNoodleThread1 extends Thread{
    private final String name;

    private final TablewarePair tablewarePair;

    public EatNoodleThread1(String name, TablewarePair tablewarePair) {
        this.name = name;
        this.tablewarePair = tablewarePair;
    }

    @Override
    public void run() {
        while(true) {
            this.eat();
        }
    }

    private void eat() {
        synchronized (tablewarePair) {
            System.out.println("eatting");
        }
    }

}

2.3哲学家吃面问题

哲学家吃面是解释操作系统中多个进程竞争资源的经典问题,每个哲学家的左右手都有吃面用的刀叉,但是不足以同时去使用,比如A哲学家想要吃面,必须拿起左手边的叉和右手边的刀,但是有可能叉和刀都被其他哲学家拿走使用,或者是手持刀等待别人放下叉等容易引起死锁的问题。

相关推荐
步行cgn44 分钟前
@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
java·spring·mvc
脏脏a1 小时前
延迟与画质:远程控制的生命线
开发语言·php
jason成都2 小时前
RTKLIB 的 Java 移植版来了:rtklib-java v3.3.0 功能全景
java·gnss·rtklib·rtklib_java
像风一样的男人@2 小时前
python --基于scp的文件传输助手
开发语言·python
一条破秋裤2 小时前
SDIO Wi‑Fi 设备的驱动向insmod + 反向rmmod
开发语言·php
LazyHunter92 小时前
JDK 28:Java 下一步方向解析与新特性展望
java·jdk
m0_639906942 小时前
Java 重写 Override 与重载 Overload 区别、访问修饰符、static 关键字
java·开发语言
Evand J2 小时前
【MATLAB例程】平面下,5基站对待定位点的定位。基于距离辅助下的AOA。完整代码下载链接
开发语言·matlab·平面·定位·导航·测距·aoa
GIS数据转换器2 小时前
基于CIM的城市智慧供热监管服务平台
java·大数据·开发语言·人工智能·后端·物联网·struts
weixin_417396832 小时前
星级酒店境外电视频道怎么合法接入?合规要点与核对清单
开发语言·前端·php·1024程序员节·综合布线