首页 > 其他分享 >聊一聊线程是如何运行的

聊一聊线程是如何运行的

时间:2025-01-03 19:34:55浏览次数:3  
标签:run 中断 println 聊一聊 线程 interrupt isInterrupted 运行

线程运行的基本原理

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异常后再次发起中断信号。

标签:run,中断,println,聊一聊,线程,interrupt,isInterrupted,运行
From: https://blog.csdn.net/weixin_74412978/article/details/144806031

相关文章

  • 继续聊一聊sqlsugar的一个机制问题
    几个月前换了新工作,从技术负责人的岗位上下来,继续回归码农写代码,在新公司中,我不是技术负责人,没太多的话语权。公司这边项目统一都是使用了SqlSguar这个orm,我也跟着使用了几个月,期间碰见了不少奇奇怪怪的问题,甚至之前特意写文章“骂”过,但是今天要聊的这个问题,至今快月余,依旧让我......
  • 大白话拆解—多线程(六)— 同步锁机制 和 synchronized
    前言:25年初,这个时候好多小伙伴都在备战期末我们新年第二天照样日更一篇,今天这篇一定会对小白非常有用的!!!因为我们会把案例到用代码实现的全过程思路呈现出来!!!我们一直都是以这样的形式,让新手小白轻松理解复杂晦涩的概念,把Java代码拆解的清清楚楚,每一步都知道他是怎么来的,为......
  • .Net程序员机会来了,微软官方新推出一个面向Windows开发者本地运行AI模型的开源工具
    想要开发AI产品的.Net程序员机会来了,这个项目应该好好研究。虽然说大模型基本都有提供网络API,但肯定没有直接使用本地模型速度快。最近微软官方新推出AIDevGallery开源项目,可以帮助Windows开发人员学习如何将具有本地模型和API的AI添加到Windows应用程序中。01项目简介AI......
  • dskquoui.dll未被指定在Windows运行,代码0xc0000020或0xc000012f解决办法
    在大部分情况下出现我们运行或安装软件,游戏出现提示丢失某些DLL文件或OCX文件的原因可能是原始安装包文件不完整造成,原因可能是某些系统防护软件将重要的DLL文件识别为可疑,阻止并放入了隔离单里,还有一些常见的DLL文件缺少是因为系统没有安装齐全的微软运行库,还有部分情况是因为......
  • dsound.dll未被指定在Windows运行,代码0xc0000020或0xc000012f解决办法
    在大部分情况下出现我们运行或安装软件,游戏出现提示丢失某些DLL文件或OCX文件的原因可能是原始安装包文件不完整造成,原因可能是某些系统防护软件将重要的DLL文件识别为可疑,阻止并放入了隔离单里,还有一些常见的DLL文件缺少是因为系统没有安装齐全的微软运行库,还有部分情况是因......
  • 异步多线程
    什么是异步多线程说的很顺口,讲起来却傻傻分不清。异步:执行某耗时操作时(文件上传、数据处理、外部服务调用)不用阻塞主线程,而是可以继续执行其他操作。多线程:并行处理不同任务的一种方式两者的关系:异步的实现不一定依赖多线程,但多线程是实现异步的一种方式故当我们说起异步......
  • QT基于互斥锁的线程同步
    多线程这个东西越接触越觉得他的强大,我高中的时候就希望自己有几个脑袋,一个看电视,一个玩,一个写作业,一心多用,这个多线程不正好就是的,本次主要是对基于互斥锁的线程同步的研究,显示最基本的互斥锁。首先是互斥锁的概念,他是来源于生产者/消费者(producer/consumer)模型,比如有一个分......
  • 在Lazarus下的Free Pascal编程教程——以数据需求拉动程序运行的模块化程序设计方法
    0.前言我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”和“2.已......
  • QT-------------多线程
    实现思路QThread类简介:QThread是Qt中用于多线程编程的基础类。可以通过继承QThread并重写run()方法来创建自定义的线程逻辑。新线程的执行从run()开始,调用start()方法启动线程。掷骰子的多线程应用程序:创建一个DiceThread类继承自QThread,在run()......
  • C++11 thread线程的使用
    C++11thread线程的使用文章目录C++11thread线程的使用构造函数1.`thread()noexcept=default;`2.`thread(thread&)=delete;`3.`thread(constthread&&)=delete;`4.`thread(thread&&__t)noexcept`5.`template<typename_Callable,typename..._Args&g......