高频面试题:如何分别用三种姿势实现三个线程交替打印0到100
最近面试遇到的一道题,需要三个线程交替打印0-100,当时对多线程并不是很熟悉因此没怎么写出来,网上搜了之后得到现
synchronized + wait/notifyAll
实现思路:判断当前打印数字和线程数的取余,不等于当前线程则处于等待状态。循环结束唤醒所有等待线程。
public class PrintExample {//创建一个公共锁对象private static final Object Lock = new Object();//执行线程数private static final int THREAD_COUNT = 3;//打印数字的起始点private static volatile int START = 0;//打印数字的结束点private static final int END = 100;private static class Print implements Runnable{private final int index;public Print(int index){this.index = index;}@Overridepublic void run() {while(START<END){synchronized (Lock){//START和线程数进行取余,如果不等于当前线程的则等待while(START % THREAD_COUNT != index){try{Lock.wait();}catch (Exception e){e.printStackTrace();}}//否则进行输出if(START<=END){System.out.println("Thread" + (index+1) + ",打印结果:" + START);}START++;//唤醒等待线程Lock.notifyAll();}}}public static void main(String[] args) {for(int i = 0; i < THREAD_COUNT; i++){new Thread(new Print(i)).start();}}}
}
ReetrantLock + await/signalAll
实现思路:实现方式和synchronized + wait/notifyAll儿乎完全一样。我们只需要4步:
1.synchronized 替换为ReentrantLock
2.根据锁对象创建一个Condition对象
3.wait替换成await
4.notifyAll 替换为 signalAll
public class PrintExample {//创建一个公共锁对象private static final ReentrantLock Lock = new ReentrantLock();//根据锁对象创建一个Condition对象private static final Condition CONDITION = Lock.newCondition();//执行线程数private static final int THREAD_COUNT = 3;//打印数字的起始点private static volatile int START = 0;//打印数字的结束点private static final int END = 100;private static class Print implements Runnable{private final int index;public Print(int index){this.index = index;}@Overridepublic void run() {while(START<END){Lock.lock();try {//START和线程数进行取余,如果不等于当前线程的则等待while(START % THREAD_COUNT != index){try{CONDITION.await();}catch (Exception e){e.printStackTrace();}}//否则进行输出if(START<=END){System.out.println("Thread" + (index+1) + ",打印结果:" + START);}START++;//唤醒等待线程CONDITION.signalAll();}catch (Exception e){e.printStackTrace();}finally {Lock.unlock();}}}public static void main(String[] args) {for(int i = 0; i < THREAD_COUNT; i++){new Thread(new Print(i)).start();}}}
}
ReetrantLock + await/signal
因为Condition相对wait/notify方式,可以唤醒指定线程。那我们就完全不用每次都唤醒全部线程,仅需要唤醒下一次需要执行的线程就可以了。
相比较 ReentrantLock + await/signalAll 改进方法:
1.去除公共的Condition对象,替换为List<Condition> conditions;
2.调用"下一个线程的"Condition对象的signal方法唤醒下一个线程;
public class PrintExample {//创建一个公共锁对象private static final ReentrantLock Lock = new ReentrantLock();//根据锁对象创建一个Condition对象//private static final Condition CONDITION = Lock.newCondition();//执行线程数private static final int THREAD_COUNT = 3;//打印数字的起始点private static volatile int START = 0;//打印数字的结束点private static final int END = 100;private static class Print implements Runnable{private final int index;private final List<Condition> conditions;public Print(int index,List<Condition> conditions){this.index = index;this.conditions = conditions;}//只唤醒下一个线程private void signalNext(){int nextIndex = (index + 1) % THREAD_COUNT;conditions.get(nextIndex).signal();}@Overridepublic void run() {while(START<END){Lock.lock();try {//START和线程数进行取余,如果不等于当前线程的则等待while(START % THREAD_COUNT != index){try{conditions.get(index).await();}catch (Exception e){e.printStackTrace();}}//否则进行输出if(START<=END){System.out.println("Thread" + (index+1) + ",打印结果:" + START);}START++;//唤醒等待线程signalNext();}catch (Exception e){e.printStackTrace();}finally {Lock.unlock();}}}public static void main(String[] args) {List<Condition> conditionList = new ArrayList<>();conditionList.add(Lock.newCondition());conditionList.add(Lock.newCondition());conditionList.add(Lock.newCondition());for(int i = 0; i < THREAD_COUNT; i++){new Thread(new Print(i,conditionList)).start();}}}
}
此处使用 List<Condition> conditions让每个线程都拥有属于自己的condition,这样可以单独唤醒和等待。
Condition是什么
概念:
condition可以理解为条件队列。当一个线程在调用了其await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现
方法:
Condition依赖于Lock接口
方法 | 解释 |
lock.newCondition() | 生成一个Condition |
await() | 对应Object的wait();使线程等待 |
signal() | 对应Object的notify();唤醒线程 |
注意:调用Condition的await()和signal()方法,都必须在lock.lock()和lock.unlock()之间使用
在生产者和消费者中Condition的执行方式:
- 当在线程Consumer中调用await方法后,线程Consumer将释放锁,并且将自己沉睡,等待唤醒。
- 这时等到线程Producer获取到锁后,开始执行任务,完毕后,调用Condition的signalall方法,唤醒线程Consumer,线程Consumer恢复执行。
以上说明Condition是一个多线程间协调通信的工具类,使得某个或某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁
相关文章:

高频面试题:如何分别用三种姿势实现三个线程交替打印0到100
最近面试遇到的一道题,需要三个线程交替打印0-100,当时对多线程并不是很熟悉因此没怎么写出来,网上搜了之后得到现 synchronized wait/notifyAll 实现思路:判断当前打印数字和线程数的取余,不等于当前线程则处于等待…...

【git】Idea撤回本地分支、或远程分支提交记录的各种实际场景操作步骤
文章目录 idea撤回本地分支、远程分支场景操作集合场景1:要撤回最后一次本地分支的提交实现效果:操作步骤: 场景2:要撤回最后一次远程分支的提交有撤销记录的:实现效果:操作步骤: 无撤销记录的&…...

FPGA SPI 驱动程序
1.引言 此驱动程序已经完成很久了,花了2个星期的时间,主要是提升程序运行的效率。最近整理文件的时候又看到了,记录一下。 2.程序框架分解 module adc7254_Ctrl(input sys_clk, //system clkc 50Minput re…...

【实战】十一、看板页面及任务组页面开发(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十七)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...

mac m1 docker 安装kafka和zookeeper
获取本地ip地址 ifconfig en0 192.168.0.105. 下面的ip都会使用到 1、拉取镜像 docker pull wurstmeister/zookeeper docker pull wurstmeister/kafka 2、启动容器 启动 zookeeper docker run -d --name zookeeper -p 2181:2181 映射 3、 启动 kafka 注意ÿ…...

宏观经济和风电预测误差分析(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

GO学习之 搜索引擎(ElasticSearch)
GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Htt…...

Sentinel —实时监控
Sentinel 提供对所有资源的实时监控。如果需要实时监控,客户端需引入以下依赖(以 Maven 为例): <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-transport-simple-http</artif…...

接口优化通用方案
目录 批量异步、回调缓存预取池化并行锁粒度索引大事务海量数据 批量 批量思想:批量操作数据库 优化前: //for循环单笔入库 for(TransDetail detail:transDetailList){ insert(detail); } 优化后: batchInsert(transDetailList); 异步、回…...

用Visual Studio 2022的.map文件来查看C++变量在内存中的布局情况
先看几个实例 代码1 #include <iostream> int data_arr[32768]; int main() {data_arr[1] 11;std::cout<<"data_arr[1]: " << data_arr[1] << std::endl;return data_arr[1]; } 上述代码在Win10 X64,MSVC Release模式下编译&…...

使用代理突破浏览器IP限制
一、实验目的: 主要时了解代理服务器的概念,同时如何突破浏览器IP限制 二、预备知识: 代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站,特别是它具有一个cac…...

HuggingFace中的 Files and versions 如何优雅下载到本地?(Python requests,tqdm)
前言 在使用huggingface把玩各种大模型时,如果选择从远程加载模型,这个过程可能因为网络问题而非常耗时甚至直接失败,所以把模型、分词器等相关文件下载到本地,再直接从本地加载就成了不可回避的流程。 在进入具体版本的模型后&…...

三、原型模式
一、什么是原型模式 原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效&a…...

transformer实现词性标注
1、self-attention 1.1、self-attention结构图 上图是 Self-Attention 的结构,在计算的时候需要用到矩阵 Q(查询), K(键值), V(值)。在实际中,Self-Attention 接收的是输入(单词的表示向量 x组成的矩阵 X) 或者上一个 Encoder block 的输出。而 Q, K, V…...

Java中异或操作和OTP算法
最近在研究加密算法,发现异或操作在加密算法中用途特别广,也特别好用。下面以Java语言为例,简单记录一下异或操作,以及在算法中的使用,包括常用的OTP算法。 一,异或操作特征 1, 相同出0&#…...

K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(下)
温故知新 📚第三章 Kubernetes各组件部署📗安装kubectl(可直接跳转到安装kubeadm章节,直接全部安装了)📕下载kubectl安装包📕执行kubectl安装📕验证kubectl 📗安装kubead…...

女子垒球运动的发展·垒球1号位
女子垒球运动的发展 1. 女子垒球运动的起源和发展概述 女子垒球运动,诞生于19世纪末的美国,作为棒球运动的衍生品,经过百年的积淀,已在全球范围内广泛传播,形成了丰富的赛事文化。她的起源,可以追溯到19世…...

Debian 30 周年,生日快乐!
导读近日是 Debian 日,也是由伊恩-默多克(Ian Murdock)创立的 Debian GNU/Linux 通用操作系统和社区支持的 Debian 项目 30 周年纪念日。 不管你信不信,从已故的伊恩-默多克于 1993 年 8 月 16 日宣布成立 Debian 项目,…...

字符串匹配的Rabin–Karp算法
leetcode-28 实现strStr() 更熟悉的字符串匹配算法可能是KMP算法, 但在Golang中,使用的是Rabin–Karp算法 一般中文译作 拉宾-卡普算法,由迈克尔拉宾与理查德卡普于1987年提出 “ 要在一段文本中找出单个模式串的一个匹配,此算法具有线性时间的平均复杂度࿰…...

傅里叶变换(FFT)笔记存档
参考博客:https://www.luogu.com.cn/blog/command-block/fft-xue-xi-bi-ji 目录: FFT引入复数相关知识单位根及其相关性质DFT过程(难点)DFT结论(重要)IDFT结论(重要)IDFT结论证明&…...

ELK安装、部署、调试 (二) ES的安装部署
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口操作ES,也可以利用Java API。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业…...

Android 13 - Media框架(8)- MediaExtractor
上一篇我们了解了 GenericSource 需要依赖 IMediaExtractor 完成 demux 工作,这一篇我们就来学习 android media 框架中的第二个服务 media.extractor,看看 IMediaExtractor 是如何创建与工作的。 1、MediaExtractorService media.extractor 和 media.p…...

Flutter 混合开发调试
针对Flutter开发的同学来说,大部分的应用还是Native Flutter的混合开发,所以每次改完Flutter代码,运行整个项目无疑是很费时间的。所以Flutter官方也给我们提供了混合调试的方案【在混合开发模式下进行调试】,这里以Android Stud…...

C语言每日一练------(Day3)
本专栏为c语言练习专栏,适合刚刚学完c语言的初学者。本专栏每天会不定时更新,通过每天练习,进一步对c语言的重难点知识进行更深入的学习。 今天练习题的关键字: 尼科彻斯定理 等差数列 💓博主csdn个人主页:…...

14、监测数据采集物联网应用开发步骤(10)
监测数据采集物联网应用开发步骤(9.2) Modbus rtu协议开发 本章节在《监测数据采集物联网应用开发步骤(7)》基础上实现可参考《...开发步骤(7)》调试工具,本章节代码需要调用modbus_tk组件,阅读本章节前建议baidu熟悉modbus rtu协议内容 组件安装modb…...

Linux禅道上修改Apache 和 MySQL 默认端口号
1. 修改Apache默认端口号 80 cd /opt/zbox/etc/apachevim httpd.conf :wq 保存 2. 修改MySQL默认端口号 3306 cd /opt/zbox/etc/mysql vim my.cnf :wq 保存 3. 重启服务 ./zbox restart...

操作教程|通过1Panel开源Linux面板快速安装DataEase
DataEase开源数据可视化分析工具(dataease.io)的在线安装是通过在服务器命令行执行Linux命令来进行的。但是在实际的安装部署过程中,很多数据分析师或者业务人员经常会因为不熟悉Linux操作系统及命令行操作方式,在安装DataEase的过…...

机器学习策略——优化深度学习系统
正交化(Orthogonalization) 老式电视机,有很多旋钮可以用来调整图像的各种性质,对于这些旧式电视,可能有一个旋钮用来调图像垂直方向的高度,另外有一个旋钮用来调图像宽度,也许还有一个旋钮用来…...

ES6中Proxy和Proxy实例
1.Proxy Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器” 使用方法 let p new Proxy(target, handler);其中,target 为被代理对象。handler 是一个对象,其声明了代理 target 的一些操作。p 是…...

UDP协议的重要知识点
UDP,即用户数据报协议(User Datagram Protocol),是一个简单的无连接的传输层协议。与TCP相比,UDP提供了更少的错误检查机制,并允许数据包在网络上更快地传输。在这篇博客中,我们将深入探讨UDP的…...