Java多线程之中断、终止、休眠、让步-04

Java多线程之中断、终止、休眠、让步

一、概述

一个线程的生命历程总是丰富多彩的,时常伴随着中断、休眠、让步甚至是最后的终止。接下来就让我们深入了解一下线程的生命历程吧!

二、中断(唯标识位)

1、定义

​ 中断可以理解为线程的一个标识位,它表示一个运行中的线程是否被其它线程进行了中断操作。中断好比其它线程对它打了个招呼,其它线程通过调用该线程的interrupt()方法对其进行中断操作。

​ 线程之中,一个线程的生命周期分为:初始、就绪、运行、阻塞以及结束。当然,其中也可以有四种状态,初始、就绪、运行以及结束。

​ 一般而言,可能有三种原因引起阻塞:等待阻塞、同步阻塞以及其他阻塞(睡眠、join或者IO阻塞);对于Java而言,等待阻塞是调用wait方法产生的,同步阻塞则是由同步块(synchronized)产生的,睡眠阻塞是由sleep产生的,join阻塞是由join方法产生的。

api 含义
public void interrupt() 中断线程,中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户
public boolean isInterrupt() 判断Thread 对象是否中断
public static boolean interrupted 判断 当前线程是否中断,并且清除当前中断状态

2、操作过程

​ 线程通过检查自身是否被中断来进行响应,线程通过isInterrupted()来进行判断是否被中断,也可以调用Thread.interrupt()来对当前线程的中断标识位进行复位。

  • 线程中断自己是被允许的;其它线程对本线程进行中断,会检测权限,checkAccess(),如果不允许,则会抛出SecurityException异常。

  • 言归正传,要中断一个Java线程,可调用线程类(Thread)对象的实例方法:interrupt();然而interrupt()方法并不会立即执行中断操作;底层实现是,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:

    • 如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException
    • 如果是java.nio.channels.InterruptibleChannel进行的io操作引起的阻塞,则会对线程抛出一个ClosedByInterruptedException
    • 如果是轮询(java.nio.channels.Selectors)引起的线程阻塞,则立即返回,不会抛出异常
  • 如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;例如,一个线程在运行状态中,其中断标志被设置为true,则此后,一旦线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被清除,重新设置为false。

    通过上面的分析,我们可以总结,调用线程类的interrupt方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常**。因此,通过interrupt方法真正实现线程的中断原理是:**开发人员根据中断标志的具体值,来决定如何退出线程.

3、原理

以下利用一个demo来讲解中断的原理

public class ThreadSuspendTest {
    static class ThreadOne extends Thread{
        public void run(){
            try {
                for (int i=0; i < 5000; i++){
                    if (this.isInterrupted()){
                      //判断是否是中断状态,如果是,就抛出异常,不执行下面的任务
                        System.out.println("已经是停止状态了,我要退出");
                        throw new InterruptedException();
                    }
                  //非中断情况下执行的任务
                    System.out.println("i = " + (i+1));
                }
            }catch (InterruptedException e){
              //中断情况下执行的任务
                System.out.println("进入InterruptThread的catch块");
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
       Thread t= new ThreadOne();
       t.start();
      //对线程进行中断
       t.interrupt();
      //中断主线程
        Thread.currentThread().interrupt();
      //判断当前线程是否中断,并清除当前线程的中断状态true->false
        System.out.println("main:" + Thread.currentThread().getName() + "是否停止1? = " + Thread.interrupted());
      //因此这里会返回false
        System.out.println("main:" + Thread.currentThread().getName() + "是否停止2? = " + Thread.interrupted());
      //而isInterrupted()是返回线程对象是否处于中断状态。
        System.out.println(t.getName() + "是否停止3? = " + t.isInterrupted());
        System.out.println(t.getName() + "是否停止4? = " + t.isInterrupted());
    }
}

对上面的代码进行分析:

  • 线程中断并非真正意义上的对线程进行中断操作,而是改变标识位。
  • 如上,使用 t.interrupt() 对t线程进行中断操作,那么,意味着t线程的中断标识位为true,这个时候,t线程内部的run方法检测到中断标识位,就抛出中断异常,执行catch块的代码,而不执行原先的非中断情况下的代码,从而达到一种由外部操作控制线程内部的运行过程的目的,这就是线程中断的真正目的。

源码层面的分析:

  • interrupt
   public void interrupt() {
     //如果不是本身线程,需要判断是否具有设置中断标识权限
        if (this != Thread.currentThread())
          //如果没有权限,会抛出异常
            checkAccess();
     //持有锁
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
              //从JNI层面去设置标志位
                interrupt0();           // Just to set the interrupt flag 仅仅只是设置标志位
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
  • interrupted
//返回“当前线程”的中断状态并且对中断状态进行重置。
public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
  • isInterrupted
//返回的是当前线程对象的中断状态,但是不重置中断状态。
public boolean isInterrupted() {
        return isInterrupted(false);
    }
	/**
     * 测试某个线程是否被中断。中断状态
     * 是重置还是不重置取决于clearinterrupt的值
     * 通过。
     */
    @HotSpotIntrinsicCandidate
    private native boolean isInterrupted(boolean ClearInterrupted);

4、中断总结

  • 线程中断并非真正意义上的对线程进行中断操作,而是改变标识位。
  • 中断标识位的设置是在JVM层面进行设置的,在源码内部调用的是JNI方法。
  • interrupt用于设置中断标识位为true;interrupted返回当前运行线程的中断状态并且重置中断状态;isInterrupted返回线程对象的中断状态但是不进行重置。
  • 对于sleep、join、wait等方法造成的中断,

三、休眠

1、定义

sleep() 的作用是让当前线程休眠,即当前线程会从“[运行状态]”进入到“[休眠(阻塞)状态]”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“[阻塞状态]”变成“[就绪状态]”,从而等待cpu的调度执行。

执行的流程如下:

在这里插入图片描述

2、作用

  • 主动让出CPU执行占有权,特别是在利用死循环进行状态的检测、监听的时候,适当的休眠降低CPU的使用率。

  • 线程池的应用,在线程池中,对空闲的线程进行休眠,而当有任务到来,就执行唤醒线程,执行对应的任务。达到线程复用的目的,而不是创建一大堆线程去完成一个个的任务而后销毁。

  • 下面结合线程中断、线程休眠做一个任务调度的小demo,也是线程池的简化原型

    public class Worker extends Thread {
        /**
         * 需要拆解的字符
         */
        private String needToSplit;
        public void setNeedToSplit(String needToSplit) {
            this.needToSplit = needToSplit;
        }
        @Override
        public void run() {
            while (true) {
                    try {
                        //当没有需要拆解的字符串时候执行休眠
                        sleep(5000L);
                    } catch (InterruptedException e) {
                        //如果检测到中断,则唤醒自我,并且执行工作。
                        if (needToSplit != null) {
                            System.out.println("我唤醒了,执行工作..." + needToSplit);
                            //执行完毕,提交任务
                            needToSplit = null;
                        }
                    }
            }
        }
    }
    
    
    public class Dispatcher {
        private static final List<Worker> workers = new LinkedList<>();
    
        public static void main(String[] args) {
            //招募三个工作线程
            for (int i = 1; i < 4; i++) {
                workers.add(new Worker());
                //同时启动三个工作线程
                workers.get(i-1).start();
            }
            TaskManager tasks = new TaskManager();
            List<String> taskList = new LinkedList<>();
            taskList.add("我是任务1");
            taskList.add("我是任务2");
            taskList.add("我是任务3");
            tasks.setStrings(taskList);
    
            for (String task : tasks.getStrings()
                    ) {
                workers.get(0).setNeedToSplit(task);
              //当有任务来了,就中断休眠,执行任务
                workers.get(0).interrupt();
            }
        }
    }
    
    public class TaskManager {
        private List<String> strings;
        public List<String> getStrings() {
            return strings;
        }
        public void setStrings(List<String> strings) {
            this.strings = strings;
        }
    }
    

3、原理

讲原理,先上源码:

public static native void sleep(long millis) throws InterruptedException;

哈哈!就是直接调用JNI,由JVM去负责调度休眠。

四、终止

1、遭到弃用的三大将

  • suspend
    • 当前线程挂起,占有资源进入一种睡眠状态,容易造成死锁问题
  • resume
    • resume本身就是对suspend的线程进行恢复,弃用了suspend,自然也就没有它什么事情了
  • stop
    • stop强制终止线程,最大的问题是没有预留时间给线程完成资源的释放工作。

五、让步

1、定义

​ 当前线程作出让步,把cpu执行权交给join方法的调用线程对象。

2、作用

  • 可以实现分流任务的处理
  • 可以实现缓存的准确清理

3、实现原理

直接上源码:

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
              //直到分线程执行完毕
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
              //特定时间的等待
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/94883950