前言
阿巴,阿巴阿巴阿巴阿巴阿巴,阿巴,阿巴阿巴

怕你们学不会,又花了几根头发想出这个demo


最近有这么个需求(真实场景)

我开了10个线程同时给用户发送消息,10分钟发一次,这其中有9个业务线程负责发消息,而剩下一个打杂线程用来获取最新消息策略(所谓策略就是指定给哪些用户发;通过哪些途径发,微信、邮件、短信等等),每次都要获取的原因是策略可能随时有变动,我设计成每次获取最新的,就能实现不重启项目灵活更改策略(策略存在数据库中)

问题来了

每个业务线程发消息都要用到最新策略,所以必须让打杂线程先执行完,而线程的调度是随机的,执行顺序由操作系统决定,我怎么让业务线程在打杂线程执行完了再执行?

这就要牵涉到线程间的通讯了,举这个栗子目的是为了让大家对线程间通讯在实际项目中的运用有个大致了解

这个场景真实存在,不过没看懂没关系

熟悉我的朋友都知道,博主暖男嘛,举的栗子当然不会这么枯燥乏味

所以

今天的主角是:阿鸡


对不起了阿鸡,我不得不这么做

故事背景:阿鸡需要先学会唱跳rap,然后开始学打篮球

翻译成多线程:一个线程负责学习唱跳rap,一个线程负责学习打篮球,而这两个线程的调度是随机的,顺序由操作系统决定,我们怎么保证阿鸡 学会唱跳rap后,再学打篮球

方法有很多,我这里列举几个常用的,足够应付所有场景了

  • 基于join
  • 基于volatile
  • 基于synchronized
  • 基于reentrantLock
  • 基于countDownLatch

不多逼逼,上才艺

先来个入门的:join

join是Thread类的方法,底层基于wait+notify,你可以把这个方法理解成插队,谁调用谁插队,但有局限性

适用于线程较少的场景,如果线程多了会造成无限套娃,有点麻烦,不够优雅

public class JoinTest {// 用来记录啊鸡学习时间static double year;public static void main(String[] args) {//线程A,练习唱跳rapThread threadA = new Thread(() -> {for (year = 0.5; year <= 5; year += 0.5) {System.out.println("开始练习唱跳rap:已练习" + year + "年");try {Thread.sleep(288);} catch (InterruptedException e) {e.printStackTrace();}//众所周知,练习两年半即可出道if (year == 2.5) {System.out.println("===========================>练习时长两年半,出道!!!");//留意下这个break,想想如果不break会怎样break;}}});//线程B,练习打篮球Thread threadB = new Thread(() -> {try {// 让threadA线程插队,threadB执行到这儿时会被阻塞,直到threadA执行完threadA.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("开始练习打篮球");});// 启动线程threadA.start();threadB.start();}
}

不管运行多少次,结果都一样,今天就是耶稣来了也一样,我说的

如果不break,那自然是等threadA执行完了threadB才开始执行

通过volatile

这种实现比较简单,也很好理解,但是性能不咋地,会抢占很多cpu资源,如非必要,不要用

public class VolatileTest {//定义一个共享变量用来线程间通信,注意用volatile修饰,保证它内存可见static volatile boolean flag = false;static double year;public static void main(String[] args) {//线程A,练习唱跳rapThread threadA = new Thread(() -> {while (true) {if (!flag) {for (year = 0.5; year <= 5; year += 0.5) {System.out.println("开始练习唱跳rap:已练习" + year + "年");try {Thread.sleep(288);} catch (InterruptedException e) {e.printStackTrace();}//众所周知,练习两年半即可出道if (year == 2.5) {System.out.println("===========================>练习时长两年半,出道!!!");// 通知threadB你可以执行了flag = true;//同样留意这个breakbreak;}}break;}}});//线程B,练习打篮球Thread threadB = new Thread(() -> {while (true) {// 监听flagif (flag) {System.out.println("开始练习打篮球");break;}}});threadA.start();threadB.start();}
}

结果与上面第一个一样,就不展示了
关于break,我这里先不说,你先猜猜结果,再复制demo去跑跑,一定要动手

synchronized、wait()、notify()三件套

wait() 和 notify()都是Object类的通讯方法,注意一点,wait和 notify需搭配synchronized使用,注意,notify不会释放锁,至于不会释放锁体现在哪儿,这个demo下面有说明

public class SynchronizedTest {static double year;public static void main(String[] args) {SynchronizedTest sync= new SynchronizedTest();sync.execute();}public void execute() {//线程A,练习唱跳rapThread threadA = new Thread(() -> {synchronized (this) {for (year = 0.5; year <= 5; year += 0.5) {try {System.out.println("开始练习唱跳rap:已练习" + year + "年");Thread.sleep(288);if (year == 2.5) {System.out.println("===========================>练习时长两年半,出道!!!");//唤醒等待中的threadB,但threadB不会立马执行,而是等待threadA执行完,因为notify不会释放锁notify();break;}} catch (InterruptedException e) {e.printStackTrace();}}}});//线程B,练习打篮球Thread threadB = new Thread(() -> {synchronized (this) {try {wait();System.out.println("开始练习打篮球");} catch (InterruptedException e) {e.printStackTrace();}}});//注意,一定要先启动B,不然会导致B永远阻塞threadB.start();threadA.start();}
}

这个threadA里面的break一定要多想想,跑一跑你就知道啥叫不会释放锁
如果没有break,threadA在唤醒threadB后,会继续执行自己的逻辑,等自己执行完了才会释放锁,这时候threadB才开始执行

基于ReentrantLock

ReentrantLock是juc包下的并发工具,也能实现,但相对复杂,需结合Condition的await和signal,底层原理有点像上面的wait和notify

这里留个课后作业:思考一下为什么unlock要放在finally里面?

public class ReentrantLockTest {static double year;public static void main(String[] args) {//实例化一个锁和ConditionReentrantLock lock = new ReentrantLock();Condition condition = lock.newCondition();//线程A,练习唱跳rapThread threadA = new Thread(() -> {lock.lock();try {for (year = 0.5; year <= 5; year += 0.5) {System.out.println("开始练习唱跳rap:已练习" + year + "年");Thread.sleep(288);//众所周知,练习两年半即可出道if (year == 2.5) {System.out.println("===========================>练习时长两年半,出道!!!");//唤醒等待中的线程condition.signal();//这里的break也是个彩蛋,去掉它触发隐藏关卡break;}}} catch (InterruptedException e) {e.printStackTrace();} finally {//解锁lock.unlock();}});//线程B,练习打篮球Thread threadB = new Thread(() -> {lock.lock();try {//让当前线程等待condition.await();System.out.println("开始练习打篮球");} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}});//必须保证B先拿到锁,不然会导致A永远阻塞threadB.start();threadA.start();}
}

基于CountDownLatch

这也是juc包下的并发工具,主要有两个常用方法,countDown和await
简单说下原理:CountDownLatch底层维护了一个计数器count,在实例化的时候设置,当调用countDown方法时,count减一,如果count在减一前已经为0,那么什么都不会发生,如果减一后变成0,则唤醒所有等待的线程;await方法会使当前线程等待,直到count为0

public class CountDownLatchTest {static double year;public static void main(String[] args) {//实例化一个CountDownLatch,count设置为1,也就是说,只要调用一次countDown方法就会唤醒线程CountDownLatch latch = new CountDownLatch(1);//线程A,练习唱跳rapThread threadA = new Thread(() -> {for (year = 0.5; year <= 5; year += 0.5) {System.out.println("开始练习唱跳rap:已练习" + year + "年");try {Thread.sleep(288);} catch (InterruptedException e) {e.printStackTrace();}//众所周知,练习两年半即可出道if (year == 2.5) {System.out.println("===========================>练习时长两年半,出道!!!");//计数器减一latch.countDown();//老规矩,去掉break触发隐藏关卡break;}}});//线程B,练习打篮球Thread threadB = new Thread(() -> {try {//阻塞当前线程,计数器为0时被唤醒latch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("开始练习打篮球");});threadA.start();threadB.start();}
}

打完收工

罗师傅镇楼

上面五个demo要是都看懂了的话,你对多线程这块也算是比较熟了,恭喜!!!

demo的threadA 中都有break,这是我专门设计的,多观察下有break和没有break的运行结果,相信你会很有收获


ok我话说完

【多线程进阶】如何保证唱跳rap打篮球的顺序相关推荐

  1. 一个摄像头就能让虚拟人唱跳rap,抖音即可玩

    允中 发自 凹非寺 量子位 | 公众号 QbitAI 全身动作捕捉,现在无需昂贵的动捕设备,只要一个摄像头就能轻松实现. 并且就在抖音上,人人都能上手体验. 上面这段虚拟数字形象跳舞的视频采用了抖音直 ...

  2. 3D模型学会了「唱、跳、Rap、篮球」,程序员们全沉迷「鸡你太美」

    继 B 站之后,GitHub 网友也开始沉迷「鸡你太美」,让 3D 姿态也学会了「唱.跳.Rap.篮球」,而且动作准确度和连贯性似乎一点也不输练习时长两年半的练习生. 看了这段 demo 之后,网友戏 ...

  3. 我写小程序像菜虚鲲——1、唱,跳,rap,篮球

    引言 大家好,我是练习时长两年半的个人练习生菜虚鲲,我喜欢唱,跳,rap,篮球,Music! 为了避免律师含,就不po鲲鲲的原图咯~ 在小作坊待久了,都忘记自己的本职工作当初进来是一枚Android开 ...

  4. JUC并发多线程进阶

    笔记整理来源 B站UP主狂神说Java https://space.bilibili.com/95256449/ JUC并发多线程进阶 1.什么是JUC 源码+官方文档 JUC是 java util ...

  5. 【Java程序设计】多线程进阶

    多线程进阶 文章目录 多线程进阶 一.线程之间的通信 二.死锁 (1)死锁相关概念 (2)死锁的例子 三.后台(守护)线程 四.线程的生命周期 五.线程的优先级 (1)优先级 (2)基于线程优先级的线 ...

  6. 操作系统--多线程进阶(上)

    目录 前言 一丶常见的锁策略 <1>乐观锁和悲观锁思想 1>乐观锁 2>悲观锁 <2>重量级锁和轻量级锁 1>重量级锁 关于用户态切换到内核态的方式 2> ...

  7. 【学习笔记】多线程进阶JUC

    JUC多线程进阶 1.什么是JUC 源码 + 官方文档 JUC是 java util concurrent 业务:普通的线程代码 Thread Runnable: 没有返回值.效率相比于Callabl ...

  8. 多线程进阶=》JUC并发编程

    多线程进阶=>JUC并发编程 1.什么是JUC ​ JUC是java.util.concurrent的简写. ​ 用中文概括一下,JUC的意思就是java并发编程工具包. ​ 并发编程的本质就是 ...

  9. Java多线程进阶面试-Atomic 原子类

    1.介绍一下 Atomic 原子类 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的. ...

  10. 多线程进阶(并发编程JUC)

    多线程进阶(并发编程JUC) 提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!! 并发编程JUC 1. 基础知识 什么是JUC(Java并发包)?并发编程的本质(充分利用CPU的资 ...

最新文章

  1. 开发人员常用SVN命令
  2. JSON的使用・小结
  3. jsp上传文件名乱码
  4. DP 之 poj 2229
  5. 微软官方office2010使用技巧宝典视频免费下载
  6. mysql where从句_MySQL死锁系列-常见加锁场景分析
  7. 如何在AS/400上发送带有颜色的MESSAGE
  8. 安卓9.0官方系统升级包_华为、荣耀公布可升级安卓10.0机型,你的手机在名单之内吗?...
  9. 安卓开发替换json字符串中的数据_22个JavaScript开发技巧合集
  10. 微信圈子将于12月28日停止运营,网友:不是微信朋友圈?
  11. 网络编程学习2-套接字编程简介
  12. struts1和struts2的区别2
  13. JAVA + LR实现apache流媒体的性能测试
  14. [SVM系列之一]白话支持向量机(SVM)
  15. Unity3D学习历程之Rect函数
  16. 内网隐蔽隧道之DNS隧道搭建(iodine)
  17. NOI 1966 玛雅历
  18. ELK企业内部日志分析系统(elasticsearch/logstash/beats/kibana)centos7详解
  19. linux( sudo bmon ) 流量监控工具----类似于 moniter interface
  20. 还在用generator生成xxx管理系统的CRUD代码?来看看我是怎么写的

热门文章

  1. Invalid use of SingleClientConnManager: connection still allocated解决方案
  2. 另一个伊甸国际服节奏榜(以下全为个人观点,仅供参考
  3. linux mint怎么切换输入法,Linux Mint安装ibus五笔和拼音输入法简明教程(示例代码)...
  4. 农历数据html,农历公历数据sql,包含闰月数据,天干地支,风水等数据.sql
  5. 如何实现chrome谷歌浏览器多开(独立环境 独立cookie)
  6. 理想气体的质量流量计算
  7. MATLAB线性回归
  8. 分布式事务CAP理论
  9. 各种工具配置忽略证书
  10. 微信小程序 条码 二维码生成