同事说:多线程好难啊,线程状态搞不懂,带你了解线程的各个状态

引言
嗯 , 疫情已经渐渐的消息了 , 许多小伙伴是不是都开始紧张的面试了呢?
做为程序员 , 多线程是必须要熟练掌握的一个知识点 , 特别是线程的基础知识 , 一定要打牢了 。
这不 , 我一个同事就找我诉苦 , 说多线程好难啊!
好吧 , 今天就给大家简单聊一下多线程的状态 。
同事说:多线程好难啊,线程状态搞不懂,带你了解线程的各个状态
文章图片
神图请收藏
多线程状态
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态 。
同事说:多线程好难啊,线程状态搞不懂,带你了解线程的各个状态
文章图片
1.新建状态(New):
当用new操作符创建一个线程时 , 例如newThread(r) , 线程还没有开始运行 , 此时线程处在新建状态 。 当一个线程处于新生状态时 , 程序还没有开始运行线程中的代码
2.就绪状态(Runnable)
一个新创建的线程并不自动开始运行 , 要执行线程 , 必须调用线程的start()方法 。 当线程对象调用start()方法即启动了线程 , start()方法创建线程运行的系统资源 , 并调度线程运行run()方法 。 当start()方法返回后 , 线程就处于就绪状态 。
处于就绪状态的线程并不一定立即运行run()方法 , 线程还必须同其他线程竞争CPU时间 , 只有获得CPU时间才可以运行线程 。 因为在单CPU的计算机系统中 , 不可能同时运行多个线程 , 一个时刻仅有一个线程处于运行状态 。 因此此时可能有多个线程处于就绪状态 。 对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(threadscheduler)来调度的 。
3.运行状态(Running)
当线程获得CPU时间后 , 它才进入运行状态 , 真正开始执行run()方法.
【同事说:多线程好难啊,线程状态搞不懂,带你了解线程的各个状态】4.阻塞状态(Blocked)
线程运行过程中 , 可能由于各种原因进入阻塞状态:
线程通过调用sleep方法进入睡眠状态;
线程调用一个在I/O上被阻塞的操作 , 即该操作在输入输出操作完成之前不会返回到它的调用者;
线程试图得到一个锁 , 而该锁正被其他线程持有;
线程在等待某个触发条件;
......
所谓阻塞状态是正在运行的线程没有运行结束 , 暂时让出CPU , 这时其他处于就绪状态的线程就可以获得CPU时间 , 进入运行状态 。
5.死亡状态(Dead)
有两个原因会导致线程死亡:
run方法正常退出而自然死亡 ,
个未捕获的异常终止了run方法而使线程猝死 。
为了确定线程在当前是否存活着(就是要么是可运行的 , 要么是被阻塞了) , 需要使用isAlive方法 。 如果是可运行或被阻塞 , 这个方法返回true;如果线程仍旧是new状态且不是可运行的 , 或者线程死亡了 , 则返回false.
各种状态下可能出现的问题
sleep和wait的区别
我们都知道的是对于sleep和wait都是会让线程出现暂停执行的状态 , 下面从几个方面进行剖析个体区别
对于sleep()方法 , 我们首先要知道该方法是属于Thread类中的 。 而wait()方法 , 则是属于Object类中的 。
sleep()方法导致了程序暂停执行指定的时间 , 让出cpu该其他线程 , 但是他的监控状态依然保持着 , 当指定的时间到了又会自动恢复运行状态 。
在调用sleep()方法的过程中 , 线程不会释放对象锁 。
而当调用wait()方法的时候 , 线程会放弃对象锁 , 进入等待此对象的等待锁定池 , 只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态 。
使用的位置不同:对于wait来说使用之前要获取到锁的存在 , 所以必须放在同步代码 , 或者同步中进行执行但是sleep来说可以放在任何的地方执行 。
sleep需要捕获异常 。 waitnotify等不需要这些 。
start和run的区别
start()方法来启动线程 , 真正实现了多线程运行 。 这时无需等待run方法体代码执行完毕 , 可以直接继续执行下面的代码 。
通过调用Thread类的start()方法来启动一个线程 , 这时此线程是处于就绪状态 , 并没有运行 。 对于多线程来说只有真正意义上调用了start方法才算是对于线程的一个启动 。
方法run()称为线程体 , 它包含了要执行的这个线程的内容 , 线程就进入了运行状态 , 开始运行run函数当中的代码 。 Run方法运行结束 , 此线程终止 。 然后CPU再调度其它线程 。
join()
join()方法使调用该方法的线程在此之前执行完毕 , 也就是等待该方法的线程执行完毕后再往下继续执行 。 注意该方法也需要捕捉异常 。
就是说让该线程在执行完RUN()方法以后再执行join方法后面的代码 , 就是说可以让两个线程合并起来 , 用于实现同步功能
yield()
该方法与sleep()类似只不过不能够由用户指定暂停多长的时间 , 并且yield()方法只能让同优先级的线程有执行的机会 。 前面提到了sleep不会释放锁标识yield也不会释放锁标识 。
实际上 , yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态 , 如有 , 则把CPU的占有权交给次线程 , 否则继续运行原来的线程 , 所以yield()方法称为“退让” , 它把运行机会让给了同等级的其他线程 。
sleep方法允许较低优先级的线程获得运行机会 , 但yield()方法执行时 , 当前线程仍处在可运行状态 , 所以不可能让出较低优先级的线程此时获取CPU占有权 。 在一个运行系统中 , 如果较高优先级的线程没有调用sleep方法 , 也没有受到I/O阻塞 , 那么较低优先级线程只能等待所有较高优先级的线程运行结束 , 方可有机会运行 。 yield()只是使当前线程重新回到可执行状态 , 所有执行yield()的线程有可能在进入到可执行状态后马上又被执行 , 所以yield()方法只能使同优先级的线程有执行的机会 。
wait()和notify()、notifyAll()
这三个方法用于协调多个线程对共享数据的存取 , 所以必须在synchronized语句块内使用 。 synchronized关键字用于保护共享数据 , 阻止其他线程对共享数据的存取 , 但是这样程序的流程就很不灵活了 , 如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制 。 wait()方法使当前线程暂停执行并释放对象锁标示 , 让其他线程可以进入synchronized数据块 , 当前线程被放入对象等待池中 。 当调用notify()方法后 , 将从对象的等待池中移走一个任意的线程并放到锁标志等待池中 , 只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程 , 则notify()不起作用 。 notifyAll()从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中 。
wait,notify阻塞唤醒确切过程?在哪阻塞 , 在哪唤醒?为什么要出现在同步代码块中 , 为什么要处于while循环中?
常见的voidwait方法有
wait(longtimeout)
wait() 。
对于无参的方法来说:在其他线程调用此对象的notify方法或者nofifyall方法前导致当前的线程处于等待的状态 。
对于有参的函数来说以上的两条成立的情况下还会在时间超时之前也是处于等待的状态 。
对于在执行完wait方法以后 。 线程会释放掉所占用的锁标识从而使线程所在的对象中的其他synchronized数据可被别的线程使用 。 因为在执行wait和notify()时候需要对锁标志进程处理和操作一个是释放锁一个是加锁所以就是来说需要要在synchronized函数中或者函数块中进行调用 , 如果不在函数中或是函数块中进行调用虽然说可以编译通过 。 但是会出现IllegalMonitorStateException异常 。
wait , notify和notifyAll这些方法为什么不在thread类里面
一个很明显的原因是Java提供的锁是对象级的而不是线程级的 , 每个对象都有锁 , 通过线程来获得由于waitnotify和notifyAll都是锁级别的的操作 , 所以把他们定义在Object类中因为锁属于对象 。
写在最后
今天聊的都是一些基础知识 , 希望在大家后面的工作和面试中都能有所帮助 。
今天就到这了 , 下次见!
同事说:多线程好难啊,线程状态搞不懂,带你了解线程的各个状态
文章图片


    推荐阅读