线程状态切换之等待队列和同步队列
转自:https://blog.csdn.net/weixin_37695911/article/details/106668435
线程的主要状态及切换:
1.初始-NEW(还未调用start())
2.运行-RUNNABLE(就绪(READY):调用了start(),运行中(RUNNING):获得了时间片这两者统称为运行)
3.阻塞-BLOCKED 因为synchronized没获取到锁的线程就会阻塞在同步队列中(同步队列中放的是尝试获取锁但失败的线程)
4.等待-WAITING(具体情况见下图,会进入等待队列,等待进入同步队列去竞争同步锁)(进入等待队列后,需要notify()唤醒,以进入同步队列)
5.超时等待-TIMED_WAITING
6.销毁-TERMINATED
等待队列(本是Object里的方法,但影响了线程)
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
与等待队列相关的步骤和图
object.notify():将任意一个线程从绿色等待队列移动到红色同步队列
待synchronized同步代码块结束后,会自动释放object锁对象,这时同步队列中的所有线程争抢对象锁
* 1.线程1获取对象A的锁,正在使用对象A。
* 2.线程1调用对象A的wait()方法。
* 3.线程1释放对象A的锁,并马上进入等待队列。
* 4.锁池里面的对象争抢对象A的锁。
* 5.线程5获得对象A的锁,进入synchronized块,使用对象A。
* 6.线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。
* 7.notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
* 8.同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。
注意:等待队列里许许多多的线程都wait()在一个对象上,此时某一线程调用了对象的notify()方法,那唤醒的到底是哪个线程?随机?队列FIFO?or sth else?java文档就简单的写了句:选择是任意性的(The choice is arbitrary and occurs at the discretion of the implementation)。
synchronized只有一个等待队列,一个同步队列
juc.locks包下有多个等待队列,一个同步队列
同步队列是在同步的环境下才有的概念,一个对象(即一个锁)永远只对应一个同步队列。
问题:为什么每个并发包中的同步器会有多个等待队列呢??
不同于synchronized同步队列和等待队列只有一个,AQS的等待队列是有多个,因为AQS可以实现排他锁(ReentrantLock)和非排他锁(ReentrantReadWriteLock——读写锁),读写锁就是一个需要多个等待队列的锁。等待队列(Condition)用来保存被阻塞的线程的。因为读写锁是一对锁,所以需要两个等待队列来分别保存被阻塞的读锁和被阻塞的写锁。
为什么要在竞争锁资源和同步队列中引入等待队列这个概念:
在synchronized中还不好解释,
但是在lock,在condition中,多少个condition就对应多少个等待队列,这样就可以区分出coder希望将当前获得锁的线程放进哪个等待队列中,以达到精准的通知/等待机制。
conditionX.await(); //将当前线程lock锁释放,把当前线程放进conditonX对应的等待队列中
conditionX.signalAll(); //把conditonX对应的等待队列中的所有线程唤醒,放进同步队列中
更深的东西涉及到AQS-抽象同步队列!
同步队列状态
1. 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁而失败的线程。
2. 当一个线程1(在等待队列中)被另外一个线程2唤醒时(在线程2里调用Object.notify()),1线程进入同步队列,去争夺对象锁。
3. 同步队列是在同步的环境下才有的概念,一个对象(即一个锁)永远只对应一个同步队列。(此处的一个对象指的就是synchronized(Object object)中的这个object)
感觉是在Object中,多个线程竞争1个锁对象,对应1个等待列队,对应1个同步队列。(正确!)
juc.locks包下,多个线程竞争1个lock锁对象,有多个等待队列,对应1个同步队列
总结:在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列。
一些我自己的总结:
1.sleep()释放CPU资源,不释放锁:同步线程之间,如果某个线程在同步代码块内调用sleep()方法,会让其他线程干等,因为sleep()方法并不会让当前线程退出代码块。sleep()方法导致了线程暂停指定的时间,让出CPU给其他线程。但是该线程的监控状态依然保持着,当其指定的时间到了又会自动恢复运行状态。
2.wait()释放CPU资源,也释放锁(使自己进入等待队列,依靠notify()以进入同步队列去竞争同步资源)
3.notify()不释放CPU资源,等到自己同步代码块执行完毕后再释放锁(即自己同步代码执行完毕后,再唤醒一个进程离开等待队列,进入同步队列,再让同步队列中的随机一个进程获得锁)
句句都是精华:
1.wait()、notify()是Object类的方法;
2.两者必须在同步代码中使用,调用者就是这个同步代码的锁对象;
3.Object类的锁对象的方法调用会影响锁对象所在的线程;
4.锁对象一旦调用wait(),会让该线程立刻释放锁对象、CPU;
5.锁对象调用notify()后,锁对象所在线程不会立即释放锁对象、CPU,而是等到同步代码执行完毕后,才释放锁对象,并唤醒一个在等待队列的线程,让其进入同步队列,让同步队列中的线程自由竞争,获得CPU以获取调用了notify()方法的线程所释放的锁对象。
关于wait、notify,自己写的一个简单demo,模仿生产者消费者,有很多地方不贴近现实,但是可以跑:
/*
* 写个生产者、消费者,
* 一共两个线程,
* 生产者每5秒生产一次,消费者每5秒消费一次,
* 初始时有一个产品,
* 仓库最多装一个。
* (利用wait()/notify())
* */
public class A20200108 {static boolean flag = true; //初始时有产品static Object lock =new Object();public static void main(String[] args) throws InterruptedException {new Thread(new Wait(),"consumer").start();TimeUnit.SECONDS.sleep(2);new Thread(new Notify(),"producer").start();}static class Wait implements Runnable{@Overridepublic void run() {while (true){try {TimeUnit.SECONDS.sleep(5);synchronized (lock){ //消费者进入仓库while(!flag){ //如果没有产品System.out.println(Thread.currentThread().getName() + "没有产品,进入等待队列");lock.wait();}System.out.println(Thread.currentThread().getName() + "有产品,消费呗");flag = false;}} catch (InterruptedException e) {e.printStackTrace();}}}}static class Notify implements Runnable{@Overridepublic void run() {while (true){try {TimeUnit.SECONDS.sleep(5);synchronized (lock){System.out.println(Thread.currentThread().getName() + "来了,准备生产,唤醒消费者");flag = true;lock.notify();}} catch (InterruptedException e) {e.printStackTrace();}}}}
}
join:由线程调用该方法。(和sleep一样,都是由Thread调用)
join的功能在内部是使用wait()方法来实现的。
作用:让调用join方法的当前线程(注意当前线程的含义,很有可能是指main线程)进入等待队列(相当于隐式调用了wait),当调用join方法的线程执行完毕时(如main线程中的一个A线程),再唤醒当前线程(如主线程)(唤醒:离开等待队列,进入同步队列)(即main线程放弃锁给其他线程后,下一个获得锁的不一定是main,main也需要去公平竞争)(相当于隐式调用了notify())
(即在主线程内,让线程A调用join(),会让主线程隐式调用了wait(),从而进入等待队列,等线程A执行完毕后,再唤醒main线程)
join():
主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
举例:可以在main线程中写
threadA.join();
/*
此代码的意思是阻塞main线程,直到threadA执行完毕(之前threadA已经开始执行了,因为join()方法必须在线程start()方法调用之后调用)(threadA.join()是Thread方法,不像wait()、notify()需要放在同步块里执行)
*/
join()方法必须在线程start()方法调用之后调用才有意义。一个线程都还未开始运行,同步是不具有任何意义的。
即先要threadA.start()后,调用threadA.join()才有意义。
感觉join和wait的区别就在唤醒时机:后者需要在别的线程里手动唤醒(by 锁对象.notify()),前者则只需要threadA.join()执行完毕,调用了这行代码的当前线程就会被唤醒。
哇,看完恍然大悟,原来线程类的join()还能和Object类中的wait()方法扯上关系
wait、notify都是Object类的方法
lock、unlock是ReentrantLock类(实现了Lock接口)的方法
前两者需要借助在synchronized中发挥作用;
后两者必须在lock中,由conditon调用。
线程状态切换之等待队列和同步队列相关推荐
- Java基础加强重温_08:线程不安全、线程同步、线程状态、线程状态切换、线程池(Executors类、newFixedThreadPool)、死锁、Lambda表达式、Stream
摘要 Java基础加强重温_08: 线程安全(线程安全概念.线程不安全案例). 线程同步(同步代码块.同步方法.Lock锁,锁对象). 线程状态(new新建.Runnable可运行.Blocked锁阻 ...
- 多线程并发-线程状态切换-后宫狗血剧情版,看完彻底掌握线程状态切换流程
- 同步器AQS中的同步队列与等待队列
在单纯地使用锁,比如ReentrantLock的时候,这个锁组件内部有一个继承同步器AQS的类,实现了其抽象方法,加锁.释放锁也只是涉及到AQS中的同步队列而已,那么等待队列又是什么呢? 当使用Con ...
- AbstractQueuedSynchronizer同步队列与Condition等待队列协同机制
之前对AbstractQueuedSynchronizer(AQS)同步队列与Condition等待队列的功能一直不是很清晰,没太清楚地区分开二者的区别和联系,最近研究了一下分享出来. 1.同步队列和 ...
- Java并发知识梳理(上):并发优缺点,线程状态转换,Java内存模型,Synchronized,Volatile,final,并发三特性,Lock与AQS,ReetrandLock
努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! 整个系列文章为Java并发专题,一是自己的兴趣,二是,这部分在实际理解上很有难度,另外在面试过程中也是经常被问到.所以在学习过程中,记 ...
- Java多线程-线程状态
线程状态 6个状态定义:java.lang.Thread.State New: 尚未启动的线程的线程状态. Runnable: 可运行线程的线程状态,等待CPU调度. Blocked: 线程阻塞等待监 ...
- 图文详解jvm中的线程状态
本文使用下面这张图详细介绍JAVA线程的六种状态 JAVA线程的六种状态详解 在java.lang.Thread类中,定义了线程的以下六种状态(同一个时刻线程只能有一种状态) NEW(新建) 这个状 ...
- 线程同步有几种方法_架构师面试必问的多线程状态切换及常用方法
架构师面试必问的多线程状态切换及常用方法 一.问题背景 Java架构师面试中,多线程状态切换及常用方法几乎是必问的,要掌握创建多线程的方式和方法. 二.创建多线程的几种方式 2.1方式一继承Threa ...
- 锁与并发工具包与线程池与LockSupport与Fork/Join框架与并行流串行流与阻塞队列与JPS,jstack命令查看死锁查看线程状态与AQS个人笔记九
朝闻道,夕死可矣 本文共计 86564字,估计阅读时长1小时 点击进入->Thread源码万字逐行解析 文章目录 本文共计 86564字,估计阅读时长1小时 一锁 二Java中13个原子操作类 ...
最新文章
- linux查看信息文件,lsof---Linux查看文件信息的强大工具
- MySQL服务器硬件和操作系统调节
- maven项目部署到Repository(Nexus)
- 如何使用Affinity Designer for mac编辑矢量曲线和形状
- Xplanner 项目规划跟踪工具
- 闲聊人工智能产品经理(AIPM)—人工智能产品经理的行业理解
- 给想进投行的学弟学妹的忠告和建议
- 注册免费域名教程(怎样免费注册域名)
- 7个必备网站,查征信、查三无产品、查老赖、查犯罪记录!
- mcdf项目验证总结-SV
- [NodeJS] Mongoose Populate 基本使用
- shell 编程三剑客之三:awk 详解
- mysql5.6.37 主从同步_Mysql5.6.x版本半同步主从复制的开启方法
- 答题软件对于私人来说安全吗?
- 最好用的地图匹配框架——基于HMM的Valhalla
- 1-2BP神经网络--Keras实现
- Excel数据录入快捷操作:
- 仿淘宝京东商品规格属性选择的最简单实现
- python偏函数应用
- Karabiner常用的自带快捷键
热门文章
- 软件整合--硬件整合--平台整合
- 集成 Tomcat、 Servlet 的生命周期
- BitMap-BitSet(JDK1.8)基本使用入门
- Junit5集成到SpringBoot工程
- MySQL数据库安装Version5.7
- Kafka万亿级消息实战干货~持续更新中
- Spring注解之@Import用法解析
- Java学习前,需要掌握的一些基础知识
- 微服务模块综合管理(模块视图管理,自动化热部署,前端资源实时刷新......)
- XStream的使用