美文网首页
聊一聊线程是如何运行的

聊一聊线程是如何运行的

作者: 小二上酒8 | 来源:发表于2022-10-06 09:11 被阅读0次

线程运行的基本原理

java应用程序中,使用new Thread().start()来启动一个线程时,底层会进行怎样的处理?我们通过一个简单的流程图来进一步分析:

如上图,java代码中创建并启动了一个线程,在JVM中调用相关方法通知到操作系统,操作系统首先接收到JVM指令会先创建一个线程出来,这时候线程并不会马上执行,它会通过操作系统CPU调度算法把该线程分配给某个CPU来执行,CPU执行任务的时候就会回调线程中的run()方法来执行相关指令。

线程的运行状态

一个线程从启动到销毁的这一个生命周期中会经历各种不同的状态,微观上java应用中线程一共分为6种状态:

  • NEW:新建状态,当执行new Thread()的时候线程处于此状态。
  • RUNNABLE:运行状态,线程调用start()方法启动线程后的状态,一般线程调用start()后会进入一个队列就绪,等获得CPU执行权后才真正开始执行线程中的run()代码块。
  • BLOCKED:阻塞状态,当线程在执行synchronized代码块,没有抢占到同步锁时会变成阻塞状态。
  • WAITING:等待状态,当调用Object.wait()方法时,线程会进入该等待状态。
  • TIMED_WAITING:超时等待状态,例如Thread.sheep(timeout)超时后会自动唤醒线程。
  • TERMINATED:终止状态,当线程中的run()方法正常执行完或者调用interrupt()的时候线程变为此状态。

从宏观上看就分为五种状态:新建、就绪、运行、等待、死亡,整体的状态运行流转如下图:

如何终止线程

首先run()方法中的指令正常运行结束后线程自然会进入终止状态。那么如果我们想要终止一个运行中的线程该怎么办?

使用stop()终止

使用stop()方法,该方式肯定是行不通的,该方法会强制停止一个线程的执行,并且会释放线程中所占用的锁,这种锁的释放是不可控的。

static class StopThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 100000; i++) {
            System.out.println("count:" + i);
        }
        System.out.println("thread run finish!");
    }
}

public static void main(String[] args) throws InterruptedException {
    StopThread stopThread = new StopThread();
    stopThread.start();
    Thread.sleep(50);
    stopThread.stop();
}

由以上代码所展示的在for循环未结束时就提前终止线程,导致最后的System.out.println("thread run finish!");不会正常执行结束。

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

进入println的源码看一下,我们就能够发现有print(x)、newLint()两个操作是原子性的,所以增加了synchronized同步锁进行保护,按正常是不应该出现问题的,但是执行stop()操作会强制释放所有锁,从而导致println()操作的原子性被破坏(上面的代码多运行几次就可能出现最后一次循环没有换行,就是存在newLine()未被执行的可能),所以实际开发过程中是一定不能使用stop()来中断线程的

使用interrupt()终止

Thread类也提供了一个方法interrupt(),从单词意义上看就是中断的意思,但实际操作上并不像stop()那样直接了断,而是通过一个信号量的方式来通知线程中断的。那么这种情况也是需要有线程自己来觉得是否终止,但是要想让线程安全中断就需要做两件事:

  • 外部线程需要发送一个中断信号给正在运行中的线程。
  • 正常运行中的线程需要根据该信号来判断是否终止线程。

根据以上的条件,我们用一个简单的例子,通过interrupt()方法进行信号传递,具体代码如下:

static class InterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    InterruptThread interruptThread = new InterruptThread();
    interruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + interruptThread.isInterrupted());
    interruptThread.interrupt();
    System.out.println("interrupt status is:" + interruptThread.isInterrupted());
}

上述代码中,首先创建并开启一个线程InterruptThread,该线程run()方法中使用while循环进行计数,判断条件为!this.isInterrupted()当前线程是否为中断状态,如果线程调用interrupt()方法那么isInterrupted()=truewhile条件不通过就停止循环打印控制台日志,线程运行结束。

从这个示例可以看出线程在调用interrupt()方法后并没有直接了断的把线程中断掉,而是通过传递消息的形式来决定是否停止线程,这样就可以在收到中断信号后继续把run()方法后面的代码指令执行完,最终达成线程安全中断的目的。

如何中断阻塞状态的线程

如果一个线程处于阻塞状态,那么能否也通过interrupt()方法进行中断?答案肯定是可以的,具体要怎么操作我们还是先上代码分析:

static class BlockedInterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    BlockedInterruptThread blockedInterruptThread = new BlockedInterruptThread();
    blockedInterruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
    blockedInterruptThread.interrupt();
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
}

上面这段代码看是使用interrupt()通知线程进行中断,但是运行结果我们会发现其实线程并没有被中断,而是打印出了异常堆栈信息并且还在运行中。

为什么我们发出interrupt()指令而为什么线程没有被中断呢?根据上面我们描述的状态流转图可以看到,线程的状态是不可能从直接状态直接终止的,而是处于阻塞状态的线程必须也只能先进入就绪状态,再进入运行状态之后才能正常终止。所以上面的代码抛出的InterruptedException异常就是因为线程处于阻塞中被提前唤醒了,也就是说在休眠阻塞时间未结束提前唤醒线程进入了就绪状态。

因此在抛出InterruptedException异常后就说明当前线程已经被唤醒正常运行了,这时候仍然要中断的话,那么就只需在catch代码块中再次对当前线程发起一次中断信号interrupt()即可,代码修改如下:

static class BlockedInterruptThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (!this.isInterrupted()) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 再次发起中断
                this.interrupt();
            }
            i++;
        }
        System.out.println("thread interrupt in:" + i);
    }
}

public static void main(String[] args) throws InterruptedException {
    BlockedInterruptThread blockedInterruptThread = new BlockedInterruptThread();
    blockedInterruptThread.start();
    TimeUnit.MILLISECONDS.sleep(50);
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
    blockedInterruptThread.interrupt();
    System.out.println("interrupt status is:" + blockedInterruptThread.isInterrupted());
}

所以说当一个阻塞任务抛出InterruptedException异常时,并不是意味着线程要终止,而是提醒当前线程有中断操作发生,捕获该异常后要怎么处理,是否继续中断可由线程本身进行把控。比如:

  • 直接捕获异常输出日志不做任何处理,线程继续运行。
  • 将异常抛出让调用方处理。
  • 打印异常信息并停止当前线程。
  • 记录日志,结合数据库或其他中间件做任务重试处理。

总结

理清线程整个生命周期中状态的变化过程,对于多线程环境出现的问题我们就能够快速的去定位分析并解决问题,特别是阻塞中的线程被提前中断要如何处理,阻塞状态的线程必须被唤醒才会继续下一步操作,这就很容易理解为什么要在捕获InterruptedException异常后再次发起中断信号。

相关文章

  • RocketMQ中的线程池是如何创建的?

    今天主要来和大家聊一聊RocketMQ中的线程池是如何创建的,如何设置线程池数量,同时也可以从中去学习到一些线程池...

  • 聊一聊多线程

    多线程分类 c语言的关键字是assign修饰结构体的关键字assign 死锁的原因 死锁总结 使用sync函数往当...

  • 聊一聊对于java并发基础的认知(二)

    聊一聊对于java并发基础的认知(一)聊一聊对于java并发基础的认知(二) 前言 本章着重聊一聊线程本身的特性,...

  • 如何生成可运行jar包

    今天我们一起来聊一聊如何生成可运行jar包,什么是可运行jar包,他有什么特点呢?这些问题我们都会在以下的内容中给...

  • RxAndroid: 高级使用(二)

    前言# 上一篇已经把一些基础的api都介绍了,这次再多聊一些api以及如何进行线程切换。 正文# 首先我们来聊一聊...

  • 聊一聊如何“理解”

    美国著名的教育学家卢姆提出知识可以分为四大类别 事实性知识 概念性知识 程序性知识 元认知知识 对于这四种不同种类...

  • 聊一聊如何“积累”(一)

    我们每天都在接受大量的知识和信息,有被动,有主动,但能被沉淀下来的成为自己的能有多少?如何才能将它们沉淀下来,而不...

  • 2021-02-20

    1.介绍一下自己,聊项目,聊优化方案,聊分布式事务 2.知道哪些线程池?什么时候到达最大线程数?到达最大线程后继续...

  • 聊一聊链路日志-异步线程

    什么是链路? 简单又狭义的理解,就是用户的一次操作,我们的程序可以将后端所有的操作记录全部串联起来。 在单体架构下...

  • 你真的会聊天术吗?看高手如何聊天“聊”出客户

    销售=聊天。 1、真正的销售是一个聊天过程;聊对方的需求、聊对方的顾虑、聊如何满足对方的需求、聊如何打消对方的顾虑...

网友评论

      本文标题:聊一聊线程是如何运行的

      本文链接:https://www.haomeiwen.com/subject/uxtvartx.html