文章目录
观察1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
观察2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
2. 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
线程状态
线程状态好比一个人的生命周期,从出生到死亡,期间会经历从婴儿到少年,从少年到青年,最终走向死亡。在java.lang.Thread.State枚举类中定义了以下六种线程的状态来描述线程的生命周期。
1.新建(NEW):当线程被创建时,它只会短暂地处于这种状态。此时它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态。
2.就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程,它就可以运行;这不同于死亡和阻塞状态。
3.阻塞(Blocked):阻塞是指一个线程被挂起,等待某个操作完成,如:等待IO操作、获取锁等。一旦阻塞线程无法执行任何操作,直到等待的条件满足并且被唤醒。线程可以通过调用Thread.sleep()方法或Object.wait()方法来进入阻塞状态。
4.等待(WAITING) : 等待状态是指线程等待另一个线程发出信号,以便进行协作。在等待状态下通常需要等待某个事件的发生。线程可以通过调用Object.wait()方法进入等待状态,等待另一个线程调用notify()或者notifyAll()方法来发送通知。
5.超时等待(TIMED_WAITING) : 具有指定等待时间的等待状态。
6.死亡(TERMINATED):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。
线程状态转移图
举个栗子,某天张三和李四打算去银行取钱,但是他们还没有行动起来,就是NEW状态,当张三和李四开始窗口前排队取钱时,就进入了Runnable状态。此状态下,排队的人员不管有没有被工作人员接待都属于Runnable状态,这个状态下的线程都具备了被分配时间片的资格,就等待调度器的调度。当张三因为一些事情需要去忙,比如需要回家拿身份证,拿手机,在发呆想妹子,此时进入了BLOCKED,WAITNG,TIME_WAITING状态。当张三和李四已经取完钱了,为TERMINATED状态。
阻塞和等待的区别:
阻塞是等待某个操作完成,而等待是等待另一个线程发出信号.
阻塞是由线程自身发起的,而等待需要与其他线程协作.
阻碍一般是在一定时间后解除,而等待是需要其他线程通知才能解除.
观察线程的状态
观察1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
示例代码
public class ThreadStateTransfer { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0; i < 10; i++) { } }, "李四"); System.out.println(t.getName() + ": " + t.getState()); t.start(); //使用 isAlive 方法判定线程的存活状态 while (t.isAlive()) { System.out.println(t.getName() + ": " + t.getState()); } System.out.println(t.getName() + ": " + t.getState()); } }
运行结果:
使用 jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED
观察2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
import java.util.concurrent.TimeUnit; public class Demo3 { private final static Object lock = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } },"t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { System.out.println("hehehe"); } },"t2"); t2.start(); } }
运行结果:
t2正常的运行打印出hehehe,t1通过 jconsole 得知进去了等待状态
中断
当银行张三进入了工作状态,他就会按照行动指南上的步骤去操作,直接完成工作为止,才结束。但是有时候会出现特殊情况,当张三给李四准备转钱的时候,老板突然给张三打来电话,说李四就是个骗子,需要立即停止转账,那老板如何通知张三停止转账呢?这就涉及了到对线程的停止方式。
停止线程的方式
1. 使用共享的标记位来沟通
示例代码
public class ThreadDemo { private static class MyRunnable implements Runnable { public volatile boolean isQuit = false; @Override public void run() { while (!isQuit) { System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ": 啊!险些误了大事"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。"); thread.start(); Thread.sleep(10 * 1000); System.out.println(Thread.currentThread().getName() + ": 老板来电话了,得赶紧通知李四对方是个骗子!"); target.isQuit = true; } }
运行结果:
main: 让李四开始转账。
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
main: 老板来电话了,得赶紧通知李四对方是个骗子!
李四: 啊!险些误了大事
Process finished with exit code 0
2. 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
Thread中中断方法介绍
使用 thread 对象的 interrupted() 方法通知线程结束.
示例代码
public class ThreadDemo3 { private static class MyRunnable implements Runnable { @Override public void run() { //Thread.currentThread()获得当前线程的引用 //这两种都可以获取中断状态 //while (!Thread.currentThread().isInterrupted()) {//检查线程的中断标记位,true-中断状态, false-非中断状态 while (!Thread.interrupted()) {//静态方法,返回当前线程的中断标记位,同时清除中断标记,改为false。比如当前线程已中断,调用interrupted(),返回true, 同时将当前线程的中断标记位改为false, 再次调用interrupted(),会发现返回false System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(Thread.currentThread().getName() + ": 有内鬼,终止交易!"); // 注意此处的 break break; //Thread.currentThread().interrupt();//设置中断位为true } } System.out.println(Thread.currentThread().getName() + ": 啊!险些误了大事"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。"); thread.start(); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + ": 老板来电话了,得赶紧通知李四对方是个骗子!"); thread.interrupt(); } }
运行结果:
main: 让李四开始转账。
李四: 别管我,我忙着转账呢!
李四: 别管我,我忙着转账呢!
main: 老板来电话了,得赶紧通知李四对方是个骗子!
李四: 有内鬼,终止交易!
李四: 啊!险些误了大事
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at org.example.thread.bit.ThreadDemo3$MyRunnable.run(ThreadDemo3.java:13)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
除了直接break退出循环,还可以通过重新设置标记位来退出,因为在触发InterruptedException异常的同时,JVM会同时把执行线程的中断标志位清除,此时调用执行线程的isInterrupted()或者interrupted(),会返回false。此时,正确的处理方式是在执行线程的run()方法中捕获到InterruptedException异常,并重新设置中断标志位。
3.观察标志位是否清除
使用 Thread.isInterrupted() , 线程中断会清除标志位
示例代码
public class ThreadDemo4 { private static class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.interrupted());// } } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); thread.start(); thread.interrupt();//设置标记为true } }
运行结果:
true
false
false
false
false
false
false
false
false
false
Process finished with exit code 0
- 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除
示例代码
public class ThreadDemo4 { private static class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().isInterrupted()); } } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); thread.start(); thread.interrupt(); } }
运行结果:
true
true
true
true
true
true
true
true
true
true