前言

最近看到很多人都在讨论多线程的问题,于是写出了这篇博客,希望可以帮到正在学习和使用这块的朋友们,首先我们先看看两个图(两个图都来自其他码农的分享)

 

这两个图是一样的逻辑,这里一起罗列出来,下面让我们用语句来简单描述下两个图:

sleep 让线程从 【running】 -> 【阻塞态】 时间结束/interrupt -> 【runnable】
wait 让线程从 【running】 -> 【等待队列】notify -> 【锁池】 -> 【runnable】

当我们看到这个图的时候首先会看到线程的几种状态,下面让我们先来分别说明一下:

线程共包括一下5种状态:

1,新建、初始状态(New) :线程对象被创建后就进入了新建状态,Thread thread = new Thread();

2,就绪(Runnable):也被称之为“可执行状态”,当线程被new出来后,其他的线程调用了该对象的start()方法,即thread.start(),此时线程位于“可运行线程池”中,只等待获取CPU的使用权,随时可以被CPU调用。进入就绪状态的进程除CPU之外,其他运行所需的资源都已经全部获得。

3,运行(Running):线程获取CPU权限开始执行。注意:线程只能从就绪状态进入到运行状态。

4,阻塞(Bloacked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行,知道线程进入就绪状态后才能有机会转到运行状态。

阻塞的情况分三种:

(01) 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池中”。进入这个状态后是不能自动唤醒的,必须依靠其他线程调用notify()或者notifyAll()方法才能被唤醒。

(02)同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被其他线程占用,则JVM会吧该线程放入“锁池”中。

(03)其他阻塞:通过调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新回到就绪状态

以上三种阻塞状态请参考上面的2个图示来理解

5,死亡(Dead):线程执行完了或因异常退出了run()方法,则该线程结束生命周期。

 wait(), notify(), notifyAll()等方法介绍

这三个方法都是定义到Object类中,wait的作用是当当前线程释放它所持有的锁进入等待状态,而notify和notifyAll则是唤醒当前对象上的等待线程。

notify()        -- 唤醒在此对象监视器上等待的单个线程。
notifyAll()    -- 唤醒在此对象监视器上等待的所有线程。

wait()           -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)     -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

wait()会使“当前线程”等待,并且会释放到它所占用的“锁标志”,从而使线程所在对象中的其他synchronized数据可以被其他线程使用。

waite()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

suspend()和 resume()方法

两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。
注意区别:
初看起来wait() 和 notify() 方法与suspend()和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的suspend()及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话),而wait() 和 notify() 这一对方法则相反。

sleep() 和 yield()方法

这两个方法都定义在Thread.java中

sleep()的作用是让当前线程休眠(正在执行的线程主动让出cpu,然后cpu就可以去执行其他任务),即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时候会大于或者等于该休眠时间,当时间过后该线程重新被会形式,他会由“阻塞状态”编程“就绪状态”,从而等待cpu的调度执行,注意:sleep方法只是让出了cpu的执行权,并不会释放同步资源锁。

yield()的作用是让步,它能够让当前线程从“运行状态”进入到“就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行,注意:这里我将上面的“具有相同优先级”的线程直接改为了线程,很多资料都写的是让具有相同优先级的线程开始竞争,但其实不是这样的,优先级低的线程在拿到cpu执行权后也是可以执行,只不过优先级高的线程拿到cpu执行权的概率比较大而已,并不是一定能拿到。

举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,

有可能是其他人先上车了,也有可能是Yield先上车了。

但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,

最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

wait和sleep的区别

相同点:

1,他们都是在多线程的环境下,都可以在程序的调用出阻塞指定的毫秒数并且返回

2,两个方法都可以通过interrupt()方法打断线程的暂停状态,但是线程会抛出InterruptedException。需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

不同点:

1,Thread类的方法:sleep(),yield()
     Object的方法:wait()和notify()、notifyAll()

2,每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

3, wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 。注意:wiat()必须放在synchronized block中,否则会在program runtime时扔出“java.lang.IllegalMonitorStateException”异常。

4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

综上可得两者最大的区别:sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。

注意:

第一:调用notify() 方法导致解除阻塞的线程是从因调用该对象的 wait()方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

第二:除了notify(),还有一个方法 notifyAll()也可起到类似作用,唯一的区别在于,调用 notifyAll()方法将把因调用该对象的 wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

java线程状态 以及 sheep()、wait()、yield() 区别的更多相关文章

  1. Java线程状态及 wait、sleep、join、interrupt、yield等的区别

    Java中的线程状态(详见Java线程状态及转换-MarchOn): wait:Object类的实例方法,释放CPU执行权,进入等待状态,直到  被中断.被拥有该对象锁的线程唤醒(notify或not ...

  2. java线程中的sleep和wait区别

                                                                            面试题:java线程中sleep和wait的区别以及其资 ...

  3. Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程

    下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...

  4. Java线程状态及同步锁

    线程的生命历程 线程的五大状态 创建状态:简而言之,当创建线程对象的代码出现的时候,此时线程就进入了创建状态.这时候的线程只是行代码而已.只有调用线程的start()方法时,线程的状态才会改变,进入就 ...

  5. Java线程状态切换以及核心方法

    1.Java线程状态 1.1 线程主要状态 ①初始(NEW):新创建了一个线程对象,但还没有调用start()方法.②运行(RUNNABLE):Java线程中将就绪(ready)和运行中(runnin ...

  6. 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

    摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...

  7. 浅谈 Java线程状态转换及控制

    线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ...

  8. Java线程状态及切换

    Java线程状态及切换 一.什么是Java线程状态 在Java程序中,用于描述Java线程的六种状态: 新建(NEW):当前线程,刚刚新建出来,尚未启动. 运行(RUNNABLE):当前线程,处于竞争 ...

  9. Java线程状态介绍

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11867086.html Java 线程状态介绍: Java官方文档中对Java线程的几种状态做 ...

随机推荐

  1. HRBUST1310 火影忍者之~鸣人 2017-03-06 16:01 104人阅读 评论(0) 收藏

    火影忍者之-鸣人 火影忍者的男主角漩涡鸣人,因为身上封印着邪恶的九尾妖狐,无父无母的他受尽了村人的冷眼与歧视,他下定决心要成为第六代火影,让所有人都认同他的存在,火影是动漫火影忍者中主人公鸣人所在的国 ...

  2. weevely入手使用笔记

    -前言 weevely是一款使用python编写的webshell工具,集webshell生成和连接于一身,采用c/s模式构建,可以算作是linux下的一款php菜刀替代工具,具有很好的隐蔽性(生成随 ...

  3. Oracle Submit Request - 请求的调用方法: FND_REQUEST.SUBMIT_REQUEST

    废话: 有一段时间没搞过开发了,做项目又要重新找回点开发的记忆.重新拾回一点点零碎. 跑多了产线,配置的一些参数也忘记得差不多了,长时间没动就是易遗忘,找点资料做个笔记就是时间保镖.   正题: FN ...

  4. php数组转成php编程代码

    将php数组转成可以在php上面运行的编程代码,支持一维及多维数组 <?php //一维数组 $test1 = array(1,2,3); //二维数组 $test2[0] = array( ' ...

  5. Tempdb--临时对象缓存

    SQL Server删除一个临时对象时,不移除该对象的条目,当再次使用时,就无须重新创建临时对象,SQL Server为临时对象缓存一个数据页和一个IAM页,并回收剩余页,如果临时表的大小超过8MB, ...

  6. centos6.6 下安装mysql5.7

    背景 没啥好说的,就是需要搭建自己的测试数据库跟研发的数据隔离开来,需要怼mysql 这个方法只适合mysql5.7 # mysql5.6的有差异 步骤 1. 确认线上mysql的版本 SELECT ...

  7. 自己的Java规范文档

    参考阿里Java规范文档 不建议使用过时方法 泛型需要加上<>,这种方式创建的对象是不推荐的. Map object = new HashMap();//禁止使用 字符串比较时,不需要大小 ...

  8. 前端必学内容:webpack3快速入门 1-23节内容参考

    前端必学内容:webpack(模块打包器) webpack3 学习内容,点击即可到达 (1).webpack快速入门——如何安装webpack及注意事项 (2).webpack快速入门——webpac ...

  9. [CSS3] 3D桃心

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. [Objective-C语言教程]命令行参数(23)

    执行时,可以将一些值从命令行传递给Objective-C程序. 这些值称为命令行参数,很多时候它们对程序很重要,特别是当想要从外部控制程序而不是在代码中对这些值进行硬编码时就很有用了. 命令行参数使用 ...