【Java系列】详解多线程(二)——Thread类及常见方法(下篇)

这篇具有很好参考价值的文章主要介绍了【Java系列】详解多线程(二)——Thread类及常见方法(下篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏】
本专栏旨在分享学习Java的一点学习心得,欢迎大家在评论区交流讨论💌

一、启动一个线程-start()方法

在操作系统中创建线程时,通常会同时创建相应的PCB并将其加入到线程管理的数据结构中,比如线程链表或线程队列(此步骤是由操作系统内核来完成的)。

调用 start 方法, 才真的在操作系统的底层创建出一个线程
解释:start 方法会通过调用系统API向操作系统请求创建一个新的线程,并分配相应的资源。这个请求将由操作系统内核处理,内核将为线程分配所需的资源。至于线程什么时候创建完成是由操作系统内核说了算的
一旦操作系统内核完成资源的分配和初始化,线程就被创建出来了。此时,操作系统会将线程状态设置为就绪状态,表示该线程已经准备好被调度执行。线程调度器将在适当的时机选择一个就绪的线程,并将其分配给可用的CPU核心来执行。调用start方法是在操作系统底层创建一个线程的触发点。

另外,start方法的执行是在一瞬间就被执行完成的。因为start方法这是负责向操作系统中请求创建出一个线程,start方法执行完成之后,代码就会立即执行start方法后续的代码逻辑。

二、终止一个线程(重点)

我们知道,run方法执行完毕之后线程就结束了。这里我们说的终止线程就是想办法让run方法快速执行完毕(正常情况下run方法没有执行完的话线程是不会突然就结束了的,除非是特别极端的特殊情况,比如拔电源)。

方式一)手动设置标志位:
我们可以通过手动设定标志位的方式来让run方法快速执行完毕。
举例代码如下:

public class Demo08 {
    public static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("hello thread!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        // 主线程这里执行一些其它的代码逻辑
        Thread.sleep(3000);
        // 修改前面设定的标志位
        isQuit = true;
        System.out.println("t线程至此终止!!!");
    }
}

运行结果如下:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
上述代码的执行结果其实是不稳定的,有时候可能会打印出来4个hello thread!!!因为sleep操作是存在误差的,比如如果真实的sleep时间比3000毫秒多的话,这里可能打印出来的就是4个hello thread!!!;如果这里真实的sleep时间比3000毫秒少的话,可能这里打印出来的就可能是两个hello thread!!!

lambda变量捕获

现在将上述的代码进行细微的更改,请看:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
为什么这里的isQuit变量如果写作成员变量就不会报错,如果写作局部局部变量就会报错呢?

上述的错误原因是由于lambda变量捕获的原因,下面我们来进行解释:Lambda 表达式可以在其定义所在的方法的上下文中访问和使用外部变量,即Lambda表达式可以捕获外部变量。被Lambda表达式捕获的外部变量必须是final或是事实上final的。
上述代码中写的lambda表达式相当于一个回调函数,它的执行时机是在一个线程被创建好了之后,在另外一个线程内部被调用执行的。所以,lambda表达式的执行时机会稍后一些。所以这就导致后续我们执行lambda表达式的时候,局部变量isQuit已经被销毁了(isQuit局部变量是跟随main方法的),换句话来说main线程已经结束了,但是lambda表达式依然是在继续执行的。
上述这种情况是客观存在的,但是如果让lambda表达式去访问一个已经销毁了的变量这显然是不合适的。所以lambda表达式引入了变量捕获这样的机制
lambda表达式的变量捕获机制:lambda表达式其实并不是直接访问的外部的变量,而是将外部的变量进行复制,即复制到lambda表达式内部(这样就解决了变量生命周期的问题)。
lambda表达式的变量捕获是有条件限制的:Lambda表达式内部访问的外部变量必须是final或者事实上是final(effectively final)。事实上是final的(即effectively final)意思就是变量可以不被final修饰,但是在代码中我们不能修改此变量,此时我们也可以称该变量是final effectively。如果对某变量进行修改的话,此时lambda表达式就不能对改变量进行变量捕获(之所以java中这样设定变量捕获是因为java是通过复制的方式来实现变量捕获机制的,如果外边的变量更改了但是lambda表达式内部的变量没有修改的话此时就很容易对代码产生歧义)。
相比之下,JS语言实现变量捕获的机制跟java中是有所区别的,JS并不是通过复制的方式来实现变量捕获的,而是通过直接改变外部变量的生命周期来保证lambda表达式可以访问到外部的变量,因此JS中的变量捕获的变量没有final或者effectively final的限制
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee

以上是手动设置标志位的方式,其实在Thread类中已经为我们提供好了标志位:Thread标志位,下面来看代码演示:

public class Demo09 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            // 这里的Thread.currentThread()其实就是t引用
            // lambda表达式是在t构造之间就被定义好的。所以编译器构造的lambda表达式看到t之后就会认为t引用是一个还没有初始化的对象。
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("hello world!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        // 这里是把标志位设置为true
        t.interrupt();
    }
}

解释:while(Thread.currentThread().isInterrupted())
Thread.currentThread()可以获取到当前线程的对象,currentThread方法是Thread类提供的一个静态方法,在哪个线程中调用这个方法我们就可以获得哪个线程对象的引用。
isInterrupted()Thread类提供的一个标志位,如果为true的话代表线程应该要结束;如果为false的话代表线程可以不必结束。isInterrupted()用于检查当前线程是否被中断,并返回一个布尔值。如果线程没有被中断,该方法将返回false;如果线程被中断,该方法将返回true。

代码运行结果如下:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
代码抛出异常后会继续打印hello world!!!,然后循环下去。
代码解释:代码中的t线程陷入睡眠之后又被interrupt唤醒了;如果我们手动设置标志位的话,是没有办法唤醒t线程的。
当一个线程正在睡眠(通过调用Thread.sleep()方法)时,如果其他线程调用了该线程的interrupt()方法,会导致正在睡眠的线程被强制中断,抛出InterruptedException异常。
注意:上述代码中,interrupt()方法是由主线程调用的。(当一个线程A需要中断另一个线程B时,它可以调用线程B的 interrupt() 方法。这个调用会将线程B的中断状态标志位设置为 true,即表示线程B被请求中断。)
在上述代码中,当t线程被interrupt()方法唤醒时(举个栗子,比如我们设定的sleep(1000),但是此时才过去10毫秒,但是线程依然会被唤醒),它的中断标志位会被设置为true。而sleep()方法会抛出InterruptedException异常,同时会清除中断标志位。因此,在上述代码中,当sleep()方法被interrupt()方法唤醒时,它会抛出InterruptedException异常,并清除中断标志位。因此,前面设置的标志位会被清除。此时,中断标志位会被重新设置为false
当中断标志位被重新设置为false之后,while循环会继续进行打印操作。

重点:上述的代码大家一定要好好进行理解,尤其是中断标志位那个地方,有很多的小点需要大家注意。

上述代码中,sleep被唤醒的同时,中断标志位被重新设定为了false;之后,线程会继续执行下去,但是如果我们想要让线程结束的话,此时我们只需要在catch之后加上break就可以了。演示代码如下:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
运行结果如下:【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
异常信息打印出来之后,代码中的while循环就会被break,即使我们不清除标志位的话,代码依然就会结束(当然加上break之后,标志位依然会被清除然后标志位会被重新设置为false)。
还有一点就是如果我们不想看到程序运行结果中的异常信息的话,我们可以直接注释掉catch中的e.printStackTrace();用于打印异常的堆栈跟踪信息)就好了

关于sleep唤醒之后可以执行哪些操作:
我们这里依然是以刚刚上述的代码进行举例,如果你忘记了的话,请看下图:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee

sleep被唤醒之后,开发人员一般可以有以下几种操作方式(给开发者留下了一定的操作空间,具体要干什么还是要根据具体的时机需求来决定):

  • 立即结束线程(如上图就是加上break之后就会立即结束线程)
  • 执行其它的一些代码逻辑,执行完这些代码逻辑之后再结束线程(即再catch中执行执行其它的代码逻辑,等到这些代码逻辑执行结束之后再break就可以了)
  • 或者忽略终止请求继续循环下去(即catch中不写break就好了)

判断中断标志位的两种方式:

  • Thread.interrupted():这是一个静态方法,此方法在判定标志位的同时会对标志位进行清除
  • Thread.currentThread().isInterrupted():这是一个成员方式(推荐使用这种方式)判断标志位的时候不会进行清除

三、等待一个线程-join()

我们知道多个线程是并发执行的,具体的执行过程都是由操作系统进行调度的,而操作系统调度线程的过程是完全随机的,随机意味着我们不知道这些线程的执行的先后顺序是怎样的。而这里的等待线程就是用来规划线程结束顺序的手段

举个栗子:现在又A、B两个线程,我们希望B线程先结束而A线程后结束,所以可以让A线程中调用B.jion()的方法,此时B线程没有执行完的话,那么A线程就会进入阻塞状态(阻塞状态的意思就是代码不继续往后执行了,即该线程暂时不去cpu上参与调度)。相当于给B线程留下了执行时间,当B线程执行完毕(即run方法执行完毕)之后,A线程就会从阻塞状态中恢复回来继续往后执行。

请看代码演示:

public class Demo10 {
    public static void main(String[] args) {
        Thread b = new Thread(() -> {
            for(int i = 0;i < 5;i++) {
                System.out.println("hello b线程!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("b线程结束了!!!");
        });
        Thread a = new Thread(() ->{
            for(int i = 0;i < 3;i++) {
                System.out.println("hello a线程!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("a线程结束了!!!");
        });
        b.start();
        a.start();
    }
}

代码演示结果如下:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
上述代码中是a线程先结束b线程后结束。
现在我们让b线程先结束a线程后结束的话,代码如下图:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
运行结果如下:
【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee
如上图b线程先结束,a线程后结束。

关于阻塞阻塞状态的解释:阻塞状态就是线程代码不在继续往后执行了,即该线程不再参与cpu调度了。
sleep方法可以让线程进入阻塞状态,但是sleep方法的阻塞是有时间限制的。
而join方法的阻塞可以说是没有时间限制,如果有两个线程A、B,倘若是B.join()的话,如果B线程没有执行结束的话,那么A线程就会死等下去即A线程将永远不被执行知道B线程执行结束。
显然,jion方法的死等这样的方式是有些不大合适的,jion方法还有另外一种形式,即public void join(long millis)(等待线程结束,最多等millis毫秒),这里的参数相当于一个最大等待时间。

另外还有一点:join方法是可以被interrupt方法唤醒的,其实sleep、join、wait方法产生阻塞之后都是可以被interrupt唤醒的(这几个方法在被唤醒之后会自动清除标志位,这一点和sleep类似)。

四、获取当前对象的引用

获取当前对象引用可以使用该方法:public static Thread currentThread();(可以返回当前线程对象的引用),在哪个线程中调用该方法就可以获取到哪个线程的引用。

五、休眠当前线程

休眠当前线程方法如下:

  • public static void sleep(long millis) throws InterruptedException :休眠当前线程millis毫秒
  • public static void sleep(long millis, int nanos) throws InterruptedException:可以更高精度的休眠

好了,本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

【Java系列】详解多线程(二)——Thread类及常见方法(下篇),JavaEE学习专栏,Java系列,java,java-ee文章来源地址https://www.toymoban.com/news/detail-764859.html

到了这里,关于【Java系列】详解多线程(二)——Thread类及常见方法(下篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JavaEE多线程】Thread类及其常见方法(上)

    🌈 座右铭 🌈 :人的一生这么长、你凭什么用短短的几年去衡量自己的一生! 💕个人主页: 清灵白羽 漾情天殇_计算机底层原理,深度解析C++,自顶向下看Java-CSDN博客 ❤️ 相关文章 ❤️:清灵白羽 漾情天殇-CSDN博客 目录 系列文章目录 前言 一、Thread构造方法         1、Th

    2024年04月22日
    浏览(32)
  • JavaEE之多线程编程:2.创建线程及Thread类常见方法(超全!!!)

    Java中创建线程的写法有很多种!!!这里介绍其中5种。 方法1:继承Thread类,重写run 创建一个类,让这个类继承自Thread父类,再重写我们的run方法就可以了。 使用Thread类,不需要import别的包,因为它是再Java.lang下面的。 注意: start() 是创建了一个新的线程,由新的线程来执

    2024年02月04日
    浏览(34)
  • 多线程系列(二) -Thread类使用详解

    在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 CPU 资源,显著的提升程序的执行效率。 其中 java.lang.Thread 是 Java 实现多线程编程最核心的类,学习 Thread 类中的方法,是学习多线程的第一步。 下面我们就一起来看看,创

    2024年02月19日
    浏览(45)
  • Jmeter系列-阶梯加压线程组Stepping Thread Group详解(6)

    tepping Thread Group是第一个自定义线程组但,随着版本的迭代,已经有更好的线程组代替Stepping Thread Group了【Concurrency Thread Group】,所以说Stepping Thread Group已经是过去式了,但还是介绍一下 有预览图显示估计的负载; 可延迟启动线程组; 可持续增加线程负载; 可设置最大负载

    2024年02月09日
    浏览(45)
  • Jmeter学习系列之七:并发线程组Concurrency Thread Group详解

    Concurrency Thread Group提供了用于配置多个线程计划的简化方法 该线程组目的是为了 保持并发水平 ,意味着如果并发线程不够,则在运行线程中启动额外的线程 和Standard Thread Group不同,它不会预先创建所有线程,因此不会使用额外的内存 对于上篇讲到的Stepping Thread Group来说,

    2024年03月12日
    浏览(44)
  • Java 线程池(Thread Pools)详解

    目录 1、线程池介绍 2、线程池执行原理 3、线程池中的阻塞队列 4、Java 线程池中的拒绝策略 5、Java 提供的创建线程池的方式 6、线程池的使用示例 7、ForkJoinPool 和 ThreadPool 的区别 1、线程池介绍          线程池是一种重用线程的机制 ,用于提高线程的利用率和管理线程的

    2024年02月05日
    浏览(40)
  • Jmeter系列- test plan【测试计划】详细讲解 、 测试计划参数详解 、基础线程组Thread Group

    测试计划描述了 Jmeter 在执行时,一系列的步骤 一个完整的测试计划包含了一个或多个【线程组、逻辑控制器、采样器、监听器、定时器、断言和配置元素】测试计划添加or删除元件 通过右键点击树中的元件,选中要添加的元件 也可以通过合并(merge)或打开(open)从文件中

    2024年02月22日
    浏览(46)
  • 【Java中的Thread线程的简单方法介绍和使用详细分析】

    提示:若对Thread没有基本的了解,可以先阅读以下文章,同时部分的方法已经在如下两篇文章中介绍过了,本文不再重复介绍!! 【Java中Tread和Runnable创建新的线程的使用方法】 【Java中的Thread线程的七种属性的使用和分析】 提示:以下是本篇文章正文内容,下面案例可供参

    2024年02月15日
    浏览(42)
  • JAVA深化篇_29—— 线程使用之线程联合以及Thread类中的其他常用方法【附有详细说明及代码案例】

    线程联合 当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。 join方法的使用 join()方法就是指调用该

    2024年02月05日
    浏览(48)
  • 【Java系列】详解多线程(一)

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习Java的一点学习心得,欢迎大家在评论区交流讨论💌 在引入多线程之前, 我们先来看一下进程是为了干什么的,

    2024年02月05日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包