上一篇Blog进行了总览,本片Blog即介绍在Java中线程的生命周期和状态是如何切换的,也就是Java多线程的调度方式,只有掌握了调度方式,才能推演出无序的多线程的执行顺序,还原程序的执行顺序。在正式学习前,先简单了解下线程的分类:
- 主线程:JVM调用程序main()所产生的线程。
- 用户线程/前台线程:前台用户创建的线程,执行任务的线程,man函数主线程,也是一个前台线程
- 守护线程/后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
新建的子线程可以是前台线程或者后台线程,前台线程必须全部执行完,即使主线程关闭掉,这时进程仍然存活。后台线程在未执行完成时,如果前台线程关掉,则后台线程也会停掉,且不抛出异常。 后台线程会随着主程序的结束而结束,但是前台进程则不会;或者说,只要有一个前台线程未退出,进程就不会终止
线程生命周期
Java线程的状态可以从java.lang.Thread
的内部枚举类java.lang.Thread$State
得知:
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
整个状态如图所示图片来源
想要实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态
- 新建(NEW): 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 可运行状态(RUNABLE): RUNNABLE状态可以认为包含两个子状态:READY和RUNNING,
- 就绪(READY): 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 可能有CPU时间片
- 运行(RUNNING): 当就绪的线程被调度并获得CPU资源时便进入运行状态 有CPU时间片
- 阻塞(BLOCKED锁阻塞): 当一个线程试图获取一个对象锁来访问资源而该对象锁正被别的线程持有时,则该线程进入BLOCKED状态,直到该线程持有对象锁,该线程转为RUNABLE状态, 无CPU时间片
- 无限期等待(WAITING): 当一个线程在等待另一个线程执行一个动作(唤醒)时,该线程处于WAITING状态,该线程不能自动唤醒,必须等待其它线程显式执行唤醒方法notify、notifyAll等
- 有限期等待(TIMED_WAITING):无需等待被显式唤醒,到达设置期限后线程会被JVM自动唤醒
- 终结(TERMINATED): 线程完成了它的全部工作run方法正常结束或线程被提前强制性地中止或出现异常导致结束
其实状态划分有很多种,这里我们就按照Java源代码的枚举状态来判定吧。
线程状态切换
六种状态的切换状态图如下图所示:
如下的执行流程更加直观一些:
新建(New)
新建一个线程分为以下两个步骤,这里设置了一个内部类,也就是Runner类:
- 首先Runner 类通过实现Runnable接口,使得该类有了多线程类的特征,
run
方法是多线程程序的一个约定。所有的多线程代码都在run
方法里面。 - 其次在实现runable接口方式启动的多线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象
参照如下的代码示例:
package com.company; //首先定义一个类实现Runnable接口,该接口只能实现一个方法即run class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner :" + i); //子线程执行内容 } } } public class Main { public static void main(String[] args) { //Thread类实际上也是实现了Runnable接口的类 Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
其运行结果如下:
NEW main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 main Thread----- :6 Process finished with exit code 0
可以看到新创建的线程处于NEW状态,主线程main执行了自己的方法。一个刚创建而尚未启动(尚未调用Thread#start()
方法)的Java线程实例的就是处于NEW状态
运行(Runnable)
可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器,当Java线程实例调用了Thread#start()
之后,就会进入RUNNABLE状态。RUNNABLE状态可以认为包含两个子状态:READY和RUNNING。
- READY:该状态的线程可以被线程调度器进行调度使之更变为RUNNING状态。
- RUNNING:该状态表示线程正在运行,线程对象的run()方法中的代码所对应的的指令正在被CPU执行。
当Java线程实例Thread#yield()
方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING转变为READY,这两种情况从线程状态Thread#getState()获取到的状态依然是RUNNABLE。
package com.company; //首先定义一个类实现Runnable接口,该接口只能实现一个方法即run class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } } public class Main { public static void main(String[] args) { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
则运行结果为:
RUNNABLE main Thread----- :0 main Thread----- :1 Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 Process finished with exit code 0
Thread线程静态方法yield
Thread.yield方法表示暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
- Thread.yield作用是让当前线程由RUNNING转变为READY,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
- 实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield从未导致线程转到等待/睡眠/阻塞状态
yield()在大多数情况下将导致线程从运行状态转到可运行状态
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); thread.start(); //启动子线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ Thread.yield(); //加入线程让步,但是状态还是RUNNABLE for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
返回结果为:
NEW RUNNABLE main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5
无限期等待(Waiting)
WAITING是无限期的等待状态,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING更变为RUNNABLE然后继续执行
LockSupport的静态方法park与unpark
可以使用LockSupport的静态方法park使当前状态转为Waiting
package com.company; import java.util.concurrent.locks.LockSupport; public class ThreadTest { public static void main(String[] args) throws Exception { Thread thread = new Thread(()-> { LockSupport.park(); }); thread.start(); Thread.sleep(50); //为了让thread先进入park块 System.out.println(thread.getState()); LockSupport.unpark(thread); System.out.println(thread.getState()); } }
执行结果为:
WAITING TERMINATED
Thread线程实例方法join
在当前线程中调用另一个线程的join()方法,则当前线程转入WAITING状态,直到另一个进程运行结束,当前线程再由阻塞转为RUNNABLE状态。
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); thread.start(); //启动子线程 System.out.println(thread.getState()); thread.join(); //thread线程执行完终结后,主线程才开始执行 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
执行结果如下:
NEW RUNNABLE Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
thread线程调用了join()方法,那么后面的main线程代码只有等到子线程结束了才能执行,在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是**如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
Object对象实例方法wait
可以使用Object对象实例方法wait方法来实现无限期等待,并用notify来进行唤醒。
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.wait(); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); thread1.start(); // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态 Thread.sleep(500); System.out.println(thread1.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
可以看到打印出来的状态为:
[2021-02-26 18:15:32]-begin... [2021-02-26 18:15:32]-thread1 got monitor lock... WAITING [2021-02-26 18:15:33]-end...
限期等待(Timed Waiting)
定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一,TIMED WAITING就是有限期等待状态,它和WAITING有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM唤醒,有点类似于现实生活中的闹钟:
Thread线程静态方法sleep
join方法的有限期等待和其无限期类似,只不过加上超时参数就行了,这里重点介绍下我们一般常用的sleep方法,join是让当前线程插队的,而sleep是让当前线程休眠的。
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); Thread.sleep(5000); //主线程休眠5秒 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
返回结果为:
RUNNABLE Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
这么看貌似和join执行结果类似,实则不然,主线程实际上在5秒后就开始运行了,只不过6次打印太快了,thread线程在5秒内就执行完了,如果把循环变为100遍,并且把休眠时间设置为1毫秒
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); Thread.sleep(1); //主线程休眠5秒 System.out.println(thread.getState()); for (int i = 0; i < 100; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 100; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
可以看到返回的部分内容,主线程的执行间或出现:
Runner Thread----- :87 main Thread----- :25 Runner Thread----- :88 main Thread----- :26 main Thread----- :27 main Thread----- :28 main Thread----- :29 main Thread----- :30 Runner Thread----- :89 main Thread----- :31 main Thread----- :32 main Thread----- :33 main Thread----- :34 main Thread----- :35 Runner Thread----- :90 main Thread----- :36
Object的实例方法wait(timeout)方法
当然也可以用wait方法来实现限期等待,需要注意的是该方法需要在同步块中进行调用
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.wait(1000); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); thread1.start(); // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态 Thread.sleep(500); System.out.println(thread1.getState()); Thread.sleep(2000); System.out.println(thread1.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
执行结果如下:
[2021-02-26 18:19:44]-begin... [2021-02-26 18:19:44]-thread1 got monitor lock... TIMED_WAITING [2021-02-26 18:19:45]-thread1 exit waiting... TERMINATED [2021-02-26 18:19:47]-end...
阻塞(Blocked)
此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者获取同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法
- 线程正在等待一个监视器锁,只有获取监视器锁之后才能进入synchronized代码块或者synchronized方法,在此等待获取锁的过程线程都处于阻塞状态。
- 线程thread步入synchronized代码块或者synchronized方法后(此时已经释放监视器锁)调用Object#wait()方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll(),但是线程T尚未退出它所在的synchronized代码块或者synchronized方法,那么线程X依然处于阻塞状态
线程被阻塞了。与等待状态的区别是:阻塞在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。依据两个场景分别举例说明:
竞争监视器锁
这种场景下,多个线程竞争监视器锁的时候,如果一个线程在获取到锁后一直不释放,那么别的线程就会被阻塞住:
package com.company; public class ThreadTest { private static final Object MONITOR = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(()-> { synchronized (MONITOR){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread1----- :" + i); //子线程执行内容 } try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } }); //在主线程里实例化该新线程 Thread thread2=new Thread(()-> { synchronized (MONITOR){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread2----- :" + i); //子线程执行内容 } } }); //在主线程里实例化该新线程 thread1.start(); //启动子线程 Thread.sleep(50); System.out.println("Runner Thread1: "+thread1.getState()); thread2.start(); //启动子线程 Thread.sleep(50); System.out.println("Runner Thread2: "+thread2.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
可以看到thread2被阻塞住了:
Runner Thread1----- :0 Runner Thread1----- :1 Runner Thread1----- :2 Runner Thread1----- :3 Runner Thread1----- :4 Runner Thread1----- :5 Runner Thread1: TIMED_WAITING Runner Thread2: BLOCKED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
Object的实例方法wait与notify、notifyAll
在同步块中还有一种场景会导致阻塞就是监视器对象的wait,从功能上来说wait就是线程A在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程B调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是线程B对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,线程B并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程(线程A),赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { Thread.sleep(1000); MONITOR.wait(); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); Thread thread2 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.notify(); Thread.sleep(2000); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now()))); } }); thread1.start(); thread2.start(); // 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,之后主线程醒了继续向下打印线程1和线程2的状态 Thread.sleep(1500); System.out.println(thread1.getState()); System.out.println(thread2.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
执行结果和解释如下:
[2021-02-26 09:15:56]-begin... [2021-02-26 09:15:56]-thread1 got monitor lock... [2021-02-26 09:15:57]-thread2 got monitor lock... BLOCKED TIMED_WAITING [2021-02-26 09:15:58]-end... [2021-02-26 09:15:59]-thread2 releases monitor lock... [2021-02-26 09:15:59]-thread1 exit waiting...
Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制
- 从语法角度,
Obj.wait()
,与Obj.notify()
必须要与synchronized(Obj)
一起使用,也就是wait与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。 - 从功能角度,wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行,notify方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
与notify方法类似的方法还有一个notifyAll,唤醒在此对象监视器上等待的所有线程。
终结(Terminated)
终结的线程对应的线程状态,此时线程已经执行完毕,TERMINATED状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()方法,Thread#run()方法执行结束之后,线程状态就会更变为TERMINATED,意味着线程的生命周期已经结束
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 Thread.sleep(50); System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
可以看到线程的终结状态:
Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
方法之间的比较和说明
我们在线程生命周期中使用了一些方法让线程进行切换,那么我们来看看这几种方法之间的对比,有什么异同:
线程中断
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程
public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 thread.interrupt(); System.out.println(thread.getState()); Thread.sleep(2000); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("interrupt----- :" ); } for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); } } }
返回结果为
RUNNABLE interrupt----- : Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
sleep和yield的区别
yield方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield方法称为“退让”,它把运行机会让给了同等优先级的其他线程,sleep则直接中断当前线程一段时间。
- sleep使线程进入Timed_Waiting状态,yield的线程依然是Runable,sleep使当前线程进入停滞状态,所以执行sleep的线程在指定的时间内肯定不会被执行;yield只是使当前线程重新回到可执行状态,所以执行yield的线程有可能在进入到可执行状态后马上又被执行
- sleep时间可设定,yield不可以,sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
- sleep不需要考虑线程优先级,yield需要,sleep 方法允许较低优先级的线程获得运行机会,但 yield方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权
在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
sleep和wait的区别与联系
两者的相同点是:
- 都可以使线程切换到TIMED_WAITING状态,它们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数释放CPU控制权,并返回
- 都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,捕获并安全结束线程
两者的区别是:
- wait方法必须放在同步块里执行,也就是必须有同步方法修饰
- wait通常被⽤于线程间交互/通信(BLOCKED状态),sleep 通常被⽤于暂停执⾏。
- sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
- wait⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify或者notifyAll⽅法,也就是锁的状态切换。
以上就是二者的异同上一篇Blog进行了总览,本片Blog即介绍在Java中线程的生命周期和状态是如何切换的,也就是Java多线程的调度方式,只有掌握了调度方式,才能推演出无序的多线程的执行顺序,还原程序的执行顺序。在正式学习前,先简单了解下线程的分类:
- 主线程:JVM调用程序main()所产生的线程。
- 用户线程/前台线程:前台用户创建的线程,执行任务的线程,man函数主线程,也是一个前台线程
- 守护线程/后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
新建的子线程可以是前台线程或者后台线程,前台线程必须全部执行完,即使主线程关闭掉,这时进程仍然存活。后台线程在未执行完成时,如果前台线程关掉,则后台线程也会停掉,且不抛出异常。 后台线程会随着主程序的结束而结束,但是前台进程则不会;或者说,只要有一个前台线程未退出,进程就不会终止
线程生命周期
Java线程的状态可以从java.lang.Thread
的内部枚举类java.lang.Thread$State
得知:
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
整个状态如图所示图片来源
想要实现多线程,必须在主线程中创建新的线程对象。Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态
- 新建(NEW): 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 可运行状态(RUNABLE): RUNNABLE状态可以认为包含两个子状态:READY和RUNNING,
- 就绪(READY): 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 可能有CPU时间片
- 运行(RUNNING): 当就绪的线程被调度并获得CPU资源时便进入运行状态 有CPU时间片
- 阻塞(BLOCKED锁阻塞): 当一个线程试图获取一个对象锁来访问资源而该对象锁正被别的线程持有时,则该线程进入BLOCKED状态,直到该线程持有对象锁,该线程转为RUNABLE状态, 无CPU时间片
- 无限期等待(WAITING): 当一个线程在等待另一个线程执行一个动作(唤醒)时,该线程处于WAITING状态,该线程不能自动唤醒,必须等待其它线程显式执行唤醒方法notify、notifyAll等
- 有限期等待(TIMED_WAITING):无需等待被显式唤醒,到达设置期限后线程会被JVM自动唤醒
- 终结(TERMINATED): 线程完成了它的全部工作run方法正常结束或线程被提前强制性地中止或出现异常导致结束
其实状态划分有很多种,这里我们就按照Java源代码的枚举状态来判定吧。
线程状态切换
六种状态的切换状态图如下图所示:
如下的执行流程更加直观一些:
新建(New)
新建一个线程分为以下两个步骤,这里设置了一个内部类,也就是Runner类:
- 首先Runner 类通过实现Runnable接口,使得该类有了多线程类的特征,
run
方法是多线程程序的一个约定。所有的多线程代码都在run
方法里面。 - 其次在实现runable接口方式启动的多线程的时候,需要通过Thread类的构造方法Thread(Runnable target) 构造出对象
参照如下的代码示例:
package com.company; //首先定义一个类实现Runnable接口,该接口只能实现一个方法即run class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner :" + i); //子线程执行内容 } } } public class Main { public static void main(String[] args) { //Thread类实际上也是实现了Runnable接口的类 Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
其运行结果如下:
NEW main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 main Thread----- :6 Process finished with exit code 0
可以看到新创建的线程处于NEW状态,主线程main执行了自己的方法。一个刚创建而尚未启动(尚未调用Thread#start()
方法)的Java线程实例的就是处于NEW状态
运行(Runnable)
可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器,当Java线程实例调用了Thread#start()
之后,就会进入RUNNABLE状态。RUNNABLE状态可以认为包含两个子状态:READY和RUNNING。
- READY:该状态的线程可以被线程调度器进行调度使之更变为RUNNING状态。
- RUNNING:该状态表示线程正在运行,线程对象的run()方法中的代码所对应的的指令正在被CPU执行。
当Java线程实例Thread#yield()
方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING转变为READY,这两种情况从线程状态Thread#getState()获取到的状态依然是RUNNABLE。
package com.company; //首先定义一个类实现Runnable接口,该接口只能实现一个方法即run class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } } public class Main { public static void main(String[] args) { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
则运行结果为:
RUNNABLE main Thread----- :0 main Thread----- :1 Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 Process finished with exit code 0
Thread线程静态方法yield
Thread.yield方法表示暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
- Thread.yield作用是让当前线程由RUNNING转变为READY,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
- 实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield从未导致线程转到等待/睡眠/阻塞状态
yield()在大多数情况下将导致线程从运行状态转到可运行状态
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); thread.start(); //启动子线程 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ Thread.yield(); //加入线程让步,但是状态还是RUNNABLE for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
返回结果为:
NEW RUNNABLE main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5 Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5
无限期等待(Waiting)
WAITING是无限期的等待状态,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING更变为RUNNABLE然后继续执行
LockSupport的静态方法park与unpark
可以使用LockSupport的静态方法park使当前状态转为Waiting
package com.company; import java.util.concurrent.locks.LockSupport; public class ThreadTest { public static void main(String[] args) throws Exception { Thread thread = new Thread(()-> { LockSupport.park(); }); thread.start(); Thread.sleep(50); //为了让thread先进入park块 System.out.println(thread.getState()); LockSupport.unpark(thread); System.out.println(thread.getState()); } }
执行结果为:
WAITING TERMINATED
Thread线程实例方法join
在当前线程中调用另一个线程的join()方法,则当前线程转入WAITING状态,直到另一个进程运行结束,当前线程再由阻塞转为RUNNABLE状态。
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 System.out.println(thread.getState()); thread.start(); //启动子线程 System.out.println(thread.getState()); thread.join(); //thread线程执行完终结后,主线程才开始执行 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
执行结果如下:
NEW RUNNABLE Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
thread线程调用了join()方法,那么后面的main线程代码只有等到子线程结束了才能执行,在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是**如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
Object对象实例方法wait
可以使用Object对象实例方法wait方法来实现无限期等待,并用notify来进行唤醒。
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.wait(); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); thread1.start(); // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态 Thread.sleep(500); System.out.println(thread1.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
可以看到打印出来的状态为:
[2021-02-26 18:15:32]-begin... [2021-02-26 18:15:32]-thread1 got monitor lock... WAITING [2021-02-26 18:15:33]-end...
限期等待(Timed Waiting)
定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一,TIMED WAITING就是有限期等待状态,它和WAITING有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM唤醒,有点类似于现实生活中的闹钟:
Thread线程静态方法sleep
join方法的有限期等待和其无限期类似,只不过加上超时参数就行了,这里重点介绍下我们一般常用的sleep方法,join是让当前线程插队的,而sleep是让当前线程休眠的。
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); Thread.sleep(5000); //主线程休眠5秒 System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
返回结果为:
RUNNABLE Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
这么看貌似和join执行结果类似,实则不然,主线程实际上在5秒后就开始运行了,只不过6次打印太快了,thread线程在5秒内就执行完了,如果把循环变为100遍,并且把休眠时间设置为1毫秒
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 System.out.println(thread.getState()); Thread.sleep(1); //主线程休眠5秒 System.out.println(thread.getState()); for (int i = 0; i < 100; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 100; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
可以看到返回的部分内容,主线程的执行间或出现:
Runner Thread----- :87 main Thread----- :25 Runner Thread----- :88 main Thread----- :26 main Thread----- :27 main Thread----- :28 main Thread----- :29 main Thread----- :30 Runner Thread----- :89 main Thread----- :31 main Thread----- :32 main Thread----- :33 main Thread----- :34 main Thread----- :35 Runner Thread----- :90 main Thread----- :36
Object的实例方法wait(timeout)方法
当然也可以用wait方法来实现限期等待,需要注意的是该方法需要在同步块中进行调用
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.wait(1000); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); thread1.start(); // 这里故意让主线程sleep 500毫秒从而让thread1执行 MONITOR.wait(1000);并且打印出其状态 Thread.sleep(500); System.out.println(thread1.getState()); Thread.sleep(2000); System.out.println(thread1.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
执行结果如下:
[2021-02-26 18:19:44]-begin... [2021-02-26 18:19:44]-thread1 got monitor lock... TIMED_WAITING [2021-02-26 18:19:45]-thread1 exit waiting... TERMINATED [2021-02-26 18:19:47]-end...
阻塞(Blocked)
此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者获取同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法
- 线程正在等待一个监视器锁,只有获取监视器锁之后才能进入synchronized代码块或者synchronized方法,在此等待获取锁的过程线程都处于阻塞状态。
- 线程thread步入synchronized代码块或者synchronized方法后(此时已经释放监视器锁)调用Object#wait()方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll(),但是线程T尚未退出它所在的synchronized代码块或者synchronized方法,那么线程X依然处于阻塞状态
线程被阻塞了。与等待状态的区别是:阻塞在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。依据两个场景分别举例说明:
竞争监视器锁
这种场景下,多个线程竞争监视器锁的时候,如果一个线程在获取到锁后一直不释放,那么别的线程就会被阻塞住:
package com.company; public class ThreadTest { private static final Object MONITOR = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(()-> { synchronized (MONITOR){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread1----- :" + i); //子线程执行内容 } try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } }); //在主线程里实例化该新线程 Thread thread2=new Thread(()-> { synchronized (MONITOR){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread2----- :" + i); //子线程执行内容 } } }); //在主线程里实例化该新线程 thread1.start(); //启动子线程 Thread.sleep(50); System.out.println("Runner Thread1: "+thread1.getState()); thread2.start(); //启动子线程 Thread.sleep(50); System.out.println("Runner Thread2: "+thread2.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } }
可以看到thread2被阻塞住了:
Runner Thread1----- :0 Runner Thread1----- :1 Runner Thread1----- :2 Runner Thread1----- :3 Runner Thread1----- :4 Runner Thread1----- :5 Runner Thread1: TIMED_WAITING Runner Thread2: BLOCKED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
Object的实例方法wait与notify、notifyAll
在同步块中还有一种场景会导致阻塞就是监视器对象的wait,从功能上来说wait就是线程A在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程B调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是线程B对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,线程B并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程(线程A),赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
package com.company; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ThreadTest { private static final Object MONITOR = new Object(); private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now()))); Thread thread1 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now()))); try { Thread.sleep(1000); MONITOR.wait(); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now()))); } }); Thread thread2 = new Thread(() -> { synchronized (MONITOR) { System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now()))); try { MONITOR.notify(); Thread.sleep(2000); } catch (InterruptedException e) { //ignore } System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now()))); } }); thread1.start(); thread2.start(); // 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,之后主线程醒了继续向下打印线程1和线程2的状态 Thread.sleep(1500); System.out.println(thread1.getState()); System.out.println(thread2.getState()); System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now()))); } }
执行结果和解释如下:
[2021-02-26 09:15:56]-begin... [2021-02-26 09:15:56]-thread1 got monitor lock... [2021-02-26 09:15:57]-thread2 got monitor lock... BLOCKED TIMED_WAITING [2021-02-26 09:15:58]-end... [2021-02-26 09:15:59]-thread2 releases monitor lock... [2021-02-26 09:15:59]-thread1 exit waiting...
Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制
- 从语法角度,
Obj.wait()
,与Obj.notify()
必须要与synchronized(Obj)
一起使用,也就是wait与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。 - 从功能角度,wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行,notify方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
与notify方法类似的方法还有一个notifyAll,唤醒在此对象监视器上等待的所有线程。
终结(Terminated)
终结的线程对应的线程状态,此时线程已经执行完毕,TERMINATED状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()方法,Thread#run()方法执行结束之后,线程状态就会更变为TERMINATED,意味着线程的生命周期已经结束
package com.company; public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 Thread.sleep(50); System.out.println(thread.getState()); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); //子线程执行内容 } } }
可以看到线程的终结状态:
Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 TERMINATED main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
方法之间的比较和说明
我们在线程生命周期中使用了一些方法让线程进行切换,那么我们来看看这几种方法之间的对比,有什么异同:
线程中断
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程
public class ThreadTest { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(new Runner(),"thread1"); //在主线程里实例化该新线程 thread.start(); //启动子线程 thread.interrupt(); System.out.println(thread.getState()); Thread.sleep(2000); for (int i = 0; i < 6; i++) { System.out.println("main Thread----- :" + i); //主线程执行内容 } } } class Runner implements Runnable { public void run(){ try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("interrupt----- :" ); } for (int i = 0; i < 6; i++) { System.out.println("Runner Thread----- :" + i); } } }
返回结果为
RUNNABLE interrupt----- : Runner Thread----- :0 Runner Thread----- :1 Runner Thread----- :2 Runner Thread----- :3 Runner Thread----- :4 Runner Thread----- :5 main Thread----- :0 main Thread----- :1 main Thread----- :2 main Thread----- :3 main Thread----- :4 main Thread----- :5
sleep和yield的区别
yield方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield方法称为“退让”,它把运行机会让给了同等优先级的其他线程,sleep则直接中断当前线程一段时间。
- sleep使线程进入Timed_Waiting状态,yield的线程依然是Runable,sleep使当前线程进入停滞状态,所以执行sleep的线程在指定的时间内肯定不会被执行;yield只是使当前线程重新回到可执行状态,所以执行yield的线程有可能在进入到可执行状态后马上又被执行
- sleep时间可设定,yield不可以,sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
- sleep不需要考虑线程优先级,yield需要,sleep 方法允许较低优先级的线程获得运行机会,但 yield方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权
在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
sleep和wait的区别与联系
两者的相同点是:
- 都可以使线程切换到TIMED_WAITING状态,它们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数释放CPU控制权,并返回
- 都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,捕获并安全结束线程
两者的区别是:
- wait方法必须放在同步块里执行,也就是必须有同步方法修饰
- wait通常被⽤于线程间交互/通信(BLOCKED状态),sleep 通常被⽤于暂停执⾏。
- sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
- wait⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify或者notifyAll⽅法,也就是锁的状态切换。
以上就是二者的异同