《JavaSE-第二十一章》之线程的状态与中断

前言

在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!”

博客主页:KC老衲爱尼姑的博客主页

博主的github,平常所写代码皆在于此

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


文章目录

  • 线程状态
    • 观察线程的状态
      • 观察1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
      • 观察2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
    • 中断
      • 停止线程的方式
        • 1. 使用共享的标记位来沟通
        • 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()方法返回,但是任务的线程还可以被中断。

线程状态转移图

《JavaSE-第二十一章》之线程的状态与中断_第1张图片

举个栗子,某天张三和李四打算去银行取钱,但是他们还没有行动起来,就是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

《JavaSE-第二十一章》之线程的状态与中断_第2张图片

《JavaSE-第二十一章》之线程的状态与中断_第3张图片

观察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 得知进去了等待状态

《JavaSE-第二十一章》之线程的状态与中断_第4张图片

中断

当银行张三进入了工作状态,他就会按照行动指南上的步骤去操作,直接完成工作为止,才结束。但是有时候会出现特殊情况,当张三给李四准备转钱的时候,老板突然给张三打来电话,说李四就是个骗子,需要立即停止转账,那老板如何通知张三停止转账呢?这就涉及了到对线程的停止方式。

停止线程的方式

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中中断方法介绍

方法 说明
public void interrupt() 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位true
public static boolean interrupted() 判断当前线程的中断标志位是否设置,调用后清除标志位,改为false
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不清除标志位
  • 使用 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异常,并重新设置中断标志位

  1. 观察标志位是否清除
  • 使用 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

你可能感兴趣的:(java)