Java 多线程 --- 按序打印

2023-10-26

给你一个类:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

  • 线程 A 将会调用 first() 方法
  • 线程 B 将会调用 second() 方法
  • 线程 C 将会调用 third() 方法
  • 请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

方法1 — 控制变量

class Foo {

    int order = 1;

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        order = 2;
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while (order != 2) {}
        printSecond.run();
        order = 3;
    }

    public void third(Runnable printThird) throws InterruptedException {
        while (order != 3) {}
        printThird.run();
    }
}
  • 上面方法会超时
  • 原因是没有实现可见性, 也就是线程没有立即得到order更新后的值, 所以一直卡在while循环里, 导致超时
  • 比如线程1将order修改为2之后, 线程2没有得到这个更新, 导致一致卡在自己的while循环中
  • 关于可见性: 线程在执行的时候会将主内存中的变量拷贝一份到工作内存中,之后线程的运行只与工作内存打交道
    在这里插入图片描述

使用volatile关键字优化

  • volatile关键字可以保证可见性, 也就是对变量的更新可以立即被其他线程所捕获
class Foo {

    volatile int order = 1;

    public Foo() {

    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        order = 2;
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while (order != 2) {}
        printSecond.run();
        order = 3;
    }

    public void third(Runnable printThird) throws InterruptedException {
        while (order != 3) {}
        printThird.run();
    }
}
  • 以上实现方法还有缺点, 就是当一个线程执行时, 其他线程会不断的执行while循环, 消耗CPU的资源
  • 可以通过synchronized + wait + notifyAll的方式解决

方法2 — synchronized + wait + notifyAll

  • 使用wait可以让目前还不能允许的线程进入等待阻塞状态, 直到正在允许的线程完成任务之后调用notifyAll唤醒.
  • 当其他线程被唤醒后, 会竞争锁
  • 当一个线程拿到锁之后, 通过flag检查是否该自己运行, 如果不该自己运行则又进入等待阻塞状态, 并且释放锁
public class Foo {
    
    private int flag = 0;
    //声明一个objetc作为锁
    private Object lock = new Object();
    public Foo() {

    }
    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (lock){
            while( flag != 0){
                //还没有轮到自己运行, 进入阻塞状态. 
                lock.wait();
            }
            printFirst.run();
            flag = 1;
            //唤醒其他在阻塞状态的线程
            lock.notifyAll();
        }
    }
    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (lock){
            while (flag != 1){
                lock.wait();
            }
            printSecond.run();
            flag = 2;
            lock.notifyAll();
        }
    }
    public void third(Runnable printThird) throws InterruptedException {
        synchronized (lock){
            while (flag != 2){
                lock.wait();
            }
            printThird.run();
            flag = 0;
            lock.notifyAll();
        }
    }
}

方法3 — 信号量

  • 也可以使用信号量控制线程的执行顺序
class Foo {
    //控制第一个和第二个线程的顺序,初始信号量为0
    private final Semaphore first_to_second =new Semaphore(0);
    //控制第二个和第三个线程的顺序, 初始信号量为0
    private final Semaphore second_to_third =new Semaphore(0);

    public Foo() {
		
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        first_to_second.release();//增加信号量, first_to_second的信号量变为0, 将线程2唤起
    }

    public void second(Runnable printSecond) throws InterruptedException {
         //尝试获取信号量, first_to_second 初始为0, count--, 信号量变为-1, 线程2被放入blocking queue
        first_to_second.acquire();
        printSecond.run();
        //增加信号量, second_to_third的信号量变为0, 将线程3唤起
        second_to_third.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
         //尝试获取信号量, second_to_third 初始为0, count--, 信号量变为-1, 线程3被放入blocking queue
        second_to_third.acquire();
        printThird.run();
    }
}
  • 关于信号量
//pseudo code
struct {
	int counter;//表示目前的资源数量
	queue q;//用于存放等待中的线程
} sem_t 

//v operation: release
signal(sem_t *s) {
	s.counter++;
	//counter小于等于0则说明有线程在排队等待消费资源,所以需要队列中的资源移出,然后唤醒线程	
	if (s.counter <= 0) {
		remove(s.q, p);
		wakeup(q);
	}
}

//p operation: wait
wait(sem_t *s) {
	s.counter--;
	//counter为负说明目前没有资源可供消费,则需要将目前进程放进等待队列, 然后block掉
	if (s.counter < 0) {
		add this thread to s.q;
		block();
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 多线程 --- 按序打印 的相关文章

随机推荐

  • 银行定期存款产品目标客户的确定——基于逻辑回归

    本篇文章将会介绍用Python分析银行定期存款产品目标客户的确定详细建模细节 业务框架分析以及模型的选择与评估分析参见上一篇文章 银行定期存款产品目标客户的确定 基于逻辑回归 建模前分析 1 导入各种模块并读取数据 2 数据预处理 维规约
  • STM32 keil中__IO得意思

    IO解释 STM32得库函数中 HAL和LL库都有 存在一个 IO得宏定义 define I volatile const lt defines read only permissions define O volatile lt defi
  • STM32配置时钟系统流程(固件库/外设标准库)

    前提 STM32F10x系列固件库 标准外设库 前言 固件库帮我们写好了 时钟系统 时钟树 的配置函数 该函数也不需要我们去调用 只要正确包含了STM32的启动文件 s文件 就行 s启动文件调用执行了时钟配置函数 先于main函数执行 s启
  • “钢铁侠”大战“机器人”!马斯克称「笼中格斗」将在 X 上直播,小扎应战:8 月 26 日如何?...

    由马斯克和扎克伯格领衔 随后引爆全网讨论的 约架 已过去快两个月 先是约定在拉斯维加斯来一场 笼中格斗 接着网友看热闹不嫌事大的做起了预告海报 最后由马斯克母亲出面叫停 当大家以为格斗一事要不了了之时 马斯克再次发声 要打 准备在 X 上直
  • 【Android】拾物App期末作业

    一 期末作业题目 校园失物 拾物APP 二 实施目的 通过本实训 使受训者可以深入理解Android相关技术 并将所学知识应用到实际的中等规模的程序设计中 同时 通过本实训 受训者可以拓展Android相关的知识 提升受训者的能力 三 实施
  • java中JDK JRE JVM的关系

    1 1 软件开发介绍 程序是为了模拟现实世界 解决显示问题而使用计算机语言编写的一系列有序的指令集合 软件 即一系列按照特定顺序组织的计算机数据和指令集合 有系统软件和应用软件之分 人机交互方式 图形化界面 GUI 命令行方式 CLI 常用
  • IO作业day5

    1 gt 使用两个线程完成两个文件的拷贝 主线程拷贝前一半内容 子线程拷贝后一半内容 并且主线程要阻塞回收子线程资源 2 gt 使用三个进程完成两个文件的拷贝 主线程拷贝前三分之一 子线程1拷贝中间三分之一 子线程2拷贝后三分之一 主线程要
  • 使用 Android 开发 MQTT 客户端

    MQTT 代表消息队列遥测传输 它是一种功能强大的消息传输协议 主要用于机器对机器 M2M 和物联网 IoT 通信上下文 MQTT 在这些情况下是首选 因为它易于实施 并且非常适合资源有限的设备 在本文中 我们将开发一个使用 MQTT 协议
  • c语言结构体简单试题,C语言6结构体练习题6

    第六章 结构体 1 下面对结构变量的叙述中错误的是 A 相同类型的结构变量间可以相互赋值 B 通过结构变量 可以任意引用它的成员 C 结构变量中某个成员与这个成员类型相同 的简单变量间可相互赋值 D 结构变量与简单变量间可以赋值 2 有枚举
  • 如何实现算法中的公平性

    机器学习的公平性问题近几年受到越来越多的关注 该领域出现了一些新的进展 机器学习训练在涉及到性别 种族等与人相关的敏感属性时 常常会由于统计性偏差 算法本身甚至是人为偏见而引入歧视性行为 由此 为消除差别影响 改进机器学习公平性 主要途径包
  • 在jsp中实现表格内设置滚动框

    当我们在页面中需要放置多条数据时 滚动框则将是一个十分不错的选择 在需要加入滚动框的表格内设置标签 table tbody style display block tbody table
  • 利用Python实现卷积神经网络的可视化

    对于深度学习这种端到端模型来说 如何说明和理解其中的训练过程是大多数研究者关注热点之一 这个问题对于那种高风险行业显得尤为重视 比如医疗 军事等 在深度学习中 这个问题被称作 黑匣子 Black Box 如果不能解释模型的工作过程 我们怎么
  • C#网络编程,多个客户端连接服务器端并发送消息

    最近学习进度到了C 网络编程 在学习这一章节的知识点 写了一些小demo 此次发表的为服务器监听端口 和多个客户端连接 获取多个客户端发来的消息 服务器端代码 using System Net using System Net Socket
  • SQL Server迭代求和

    drop table t geovindu create table t geovindu xid int IDENTITY 1 1 price money DebitCredit VARCHAR 2 adate datetime defa
  • Android学习之 Scroller的介绍与使用

    类概述 Android里Scroller类是为了实现View平滑滚动的一个Helper类 通常在自定义的View时使用 在View中定义一个私有成员mScroller new Scroller context 设置mScroller滚动的位
  • 微服务工程搭建过程中的注意点

    1 父工程pom xml文件 1 父工程的maven坐标 2 packaging使用pom 原因 在Spring Cloud微服务工程中 通常会采用多模块的方式进行开发 父工程的pom文件中的packaging标签设置为pom 是因为父工程
  • Spring Framework 入门(一)

    Spring Framework各模块作用介绍 可以参考spring framework的github项目 源码地址 https github com spring projects spring framework 下面我们分别了解下各个
  • SQL所有关键字及其作用:

    以下是MySQL的所有关键字及其作用 ADD 在表中添加新的列或索引 ALL 返回满足条件的所有行 包括重复行 ALTER 修改表的结构 如添加 修改或删除列 ANALYZE 分析并收集表的统计信息 用于优化查询 AND 用于多条件查询的逻
  • wedo2.0编程模块介绍_西门子S7-200 SMART硬件和编程软件简介

    前文给大家简单的讲介绍了一下PLC编程涉及的一些概念型知识 本文开始实践 今天带来的是SIMATIC S7 200 SMART硬件和编程软件简介 SIMATIC S7 200 SMART 是西门子公司经过大量市场调研 为中国客户量身定制的一
  • Java 多线程 --- 按序打印

    Java 多线程 按序打印 方法1 控制变量 使用volatile关键字优化 方法2 synchronized wait notifyAll 方法3 信号量 给你一个类 public class Foo public void first