【JavaEE】浅谈线程(二)

news2024/11/16 7:28:18

线程

线程的常见属性

线程属性可以通过下面的表格查看。
•ID 是线程的唯⼀标识,不同线程不会重复
• 名称是各种调试⼯具⽤到(如jconsoloe)
• 状态表示线程当前所处的⼀个情况,下⾯我们会进⼀步说明
• 优先级高的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运⾏。
• 是否存活,即简单的理解,为 run 方法是否运⾏结束
• 线程的中断问题,下⾯我们进⼀步说明
在这里插入图片描述

线程的状态

线程状态分为New、Terminated、Runnable、Waiting、Timed_Waiting、Blocked

观测线程状态可以通过jconsole观察。

  • New 该状态下存在Thread对象,还没有调用start,系统内部PCB还未创建
  • Terminated 该状态下表示线程已终止,内部创建的PCB已经销毁,Thread对象还在
  • Runnable 该状态下表示线程正在CPU上执行,随时可以被调度。
  • Waiting 死等状态进入的阻塞
  • Timed_Waiting 带有规定时间的等待
  • Blocked 进行锁竞争后出现的阻塞状态

线程状态之间的转换图(省略版)
在这里插入图片描述

线程是否存活

在线程生命周期中,根据观察线程中的PCB是否存在。

Thread t = new Thread(()->{
	
});

在这段代码中,线程t只是初始化了一个Thread对象,但是其中的PCB只有在执行了t.start()以后才会被创建出来。

public static void main(String[] args) {
        Thread t = new Thread(()->{
            
        });
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
    }

在上面的代码中,线程t在启动以后创建了PCB,但是因为t中代码块没有任何代码,所以线程t很快就运行结束了,内核中的线程和PCB马上就被销毁了。而在main线程中,通过sleep停止了2s,因此线程t的PCB被销毁,而t这个对象并没有被gc回收,t仍然是存在的。
在下面的代码中,我加上了t=null,通过这个方法以求解决t对象能够被gc回收。但显然是不行的,通过t=null,可能会导致t线程还没结束,t对象就为空了。

public static void main(String[] args) {
        Thread t = new Thread(()->{
            
        });
        t.start();
     	t = null;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
    }

因此,Thread给了我们一种属性:isAlive,是否存活。通过这个属性我们可以查看到线程是否存在,并对线程t进行操作。

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello ");
            }
            System.out.println("线程t是否存活:"+Thread.currentThread().isAlive());
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("线程t是否存活:"+ t.isAlive());
    }

结果如下图
在这里插入图片描述

前台线程和后台线程的关系与区别

线程是并发执行的。当然,当线程多了以后,为了能够不让某些线程影响整个进程的结束,更好的利用系统资源。于是为线程设计了前台和后台两个线程方式。在前台线程中,如果本线程运行没有结束,则此时Java进程也无法结束。在后台线程中,即使该线程还处于执行阶段,当前台线程都结束以后,意味着整个Java进程即将结束。那么这时候后台线程就不得不停止执行了。
简单来说,前台线程决定了Java进程的时间,后台线程无法控制整个进程的时间,只能被迫跟着前台线程的结束而结束。
前台线程也可以是多个的,只有最后一个前台线程结束,整个Java进程才结束。

设置线程的前后台

在Java中,main线程,以及默认情况下的线程都属于前台线程。而改变线程前后台的方式可以通过修改线程前后台属性: t.setDaemon(true) 将对应线程转变为后台线程。

接下来使用一个例子进行简单演示
public class Demo1 {
    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            while (true) {
                for (int i = 0;i<5;i++) {
                    System.out.println("hello thread");
                }
            }
            
        });
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("hello main");
        }
    }
}

在上面的代码中,我们可以看到,理论上来说线程t应当是进入死循环不断打印"hello thread"的,但是我在线程启动之前,将t线程设置为后台线程。因此,在main线程运行结束以后,垃圾回收机制gc将main线程回收,前台线程结束,后台线程t自然也只能被迫结束。
在这里插入图片描述

线程核心操作

启动 t.start()

通过多次的练习,已经明白t.start()执行后创建线程PCB,真正创建线程并开始执行。不再过多赘述

终止 interrupt()

在Java中,终止线程不是简单的停止线程的执行。而是对线程进行提醒,提醒线程是否停止,而真正做决定的还是该线程本身。
1)通过变量控制终止线程

	private static boolean isRunning=true;
	public static void main(String[] args) throws InterruptedException {
         // boolean isRunning = true;//变量捕获
        Thread t2 = new Thread(()->{
            while (isRunning) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t2线程被结束了");
        });
        t2.start();

        Thread.sleep(4000);
        System.out.println("控制t2线程结束");
        isRunning = false;
    }

结果如下图
在这里插入图片描述
2)使用线程的interrupt和isInterrupted方法
通过下面的代码进行解释,在这个代码块中,线程t3通过isInterrupted判断是否结束这个循环,而在main线程中,通过t.interrupt方法,将这个值修改为false,抛出RuntimeException异常,结束了当前的线程。

public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(()->{

            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException();//抛出异常并结束
                }
            }
            System.out.println("t2线程被结束了");
        });
        t3.start();

        Thread.sleep(3000);

        t3.interrupt();
    }

在这里插入图片描述
3)提醒线程而不终止
在下面的代码中,修改了boolean之后,线程t3并没有停止线程,而是打印出异常信息之后继续执行。
在这里出现了一个问题,为什么通过interrupt方法之后修改了boolean值,但是在线程抛出异常之后仍然在继续打印呢?
首先,interrupt方法修改了boolean值的标志位修改为true,在slee过程中,通过interrupt方法强制唤醒线程,在强制唤醒后清除了刚刚的标志位,重新修正回flase。

public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(()->{

            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                     e.printStackTrace(); //打印异常信息而不结束
                    //throw new RuntimeException();//抛出异常并结束
                }
            }
            System.out.println("t2线程被结束了");
        });
        t3.start();

        Thread.sleep(3000);

        t3.interrupt();
    }

在这里插入图片描述

等待join()

因为线程是抢占式执行的,其调度顺序在系统中是无序的。而这种无序的执行方式不在程序猿的掌控之中,因此我们希望能够通过一些方式控制这些线程。join(等待)就是其中一种方式。

  • 基本用法
    在下面的代码块中,分为main线程和t两个线程。我们可以注意到,与之前的写法其实是大差不差的,唯一区别的是在try-catch包裹下出现的t.join()方法。
    在main线程中使用t.join的作用让main线程进入阻塞等待状态,t线程执行完之后main线程才会接着往下执行。
    通过这样的方式在一定程度上解决了线程的控制问题。
public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("thread end");
        });
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < 5; i++) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("main end");
    }

因此我们可以看到结果如下图所示:
在这里插入图片描述

  • 多个线程的join
    在多个线程等待代码了解之前,我们必须了解的是在main线程中调用t1.join,是main线程等待t1,而与其他线程无关,main线程是不会等待其他线程的。当多线程的情况下也是这般理解的。
    在下面的代码中,存在t1和t2两个线程,在main线程中,同时调用t1.join()和t2.join() 这意味着main线程只有等t1线程和t2线程全部执行完毕以后才会往下执行。t1和t2线程各自调度执行,互不影响。 所需的时间是t1线程和t2线程运行时间较长的。
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1 end");
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 end");
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("main end");
    }
  • 有限时间内等待
    在上面两个情况下,对于t.join()的方法,只有等待线程t运行结束以后才能进行下一步,属于死等状态。那么如果出现意料之外的情况,线程t无法结束,那么整个程序都会因此卡住,无法执行后续的逻辑,极大降低了系统的容错。
    因此Java提供了join带参数的两种方法。通过这两种方法,当线程超过规定时间而不结束,则主线程不再等待,继续执行下面的逻辑。
    在这里插入图片描述
    接下来简单写个demo
    在这个demo中,存在三个线程分别为t1、t2、main。在代码中我通过t1.join()让两个线程都进行阻塞状态等待线程t1结束。而对于t2,不相同的是我只让main线程等待2s,超过两秒之后,main线程不再是阻塞状态,而是执行下面的逻辑。
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            System.out.println("t1 end");
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("hello t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2 end");
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join(2000);//等待2s后main线程自己走了
        for (int i = 0; i < 5; i++) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("main end");
    }

因此代码结果如下:
在这里插入图片描述

总结

多线程有许多常见属性以及各类用法,都是需要熟练掌握的。
源码:多线程相关代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1860907.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从公共仓库拉取推送的镜像并启动_将镜像内部目录挂载到外部目录使用_从镜像中复制文件到本机目录_从本机目录复制文件到镜像中---分布式云原生部署架构搭建010

然后我们再去找一个机器 docker ps docker rm -f 0ab docker images docker rmi guignginx 把这个机器的之前的这个镜像,在运行的 和 之前的都删除掉 然后我们去仓库中,拉取我们刚刚推送的 可以看到右边是命令 docker pull leifengyang/guignginx:v1.0 然后再来看总结命…

Unity踩坑记录

1. 如果同时在父物体和子物体上挂载BoxCollider&#xff0c;那么当使用&#xff1a; private void OnTriggerEnter2D(Collider2D collision){if (collision.CompareTag("CardGroup")){_intersectCardGroups.Add(collision.GetComponent<CardGroup>());}} 来判…

基于Java微信小程序民宿短租系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

精通pip:Python开发者的必备技能

目录 1. 安装 pip 2. 使用 pip 安装包 3. 卸载包 4. 更新包 5. 列出已安装的包 6. 搜索包 7. 使用 requirements.txt 文件安装多个包 8. 升级 pip 自身 9. 虚拟环境 10. 冻结依赖 11. 使用国内镜像源 12. 安装特定版本的包 13. 批量安装包 14. 显示帮助信息 15.…

常微分方程算法之编程示例三(预估-校正法)

目录 一、研究问题 二、C代码 三、计算结果 一、研究问题 本节我们采用预估校正法&#xff08;改进欧拉法&#xff09;求解算例。 预估-校正法的原理及推导请参考&#xff1a; 常微分方程算法之预估-校正法&#xff08;改进Euler法&#xff09;_、改进欧拉法-CSDN博客https…

东昂科技从创业板改道北交所:大客户依赖症明显,巨额分红又募投补流

《港湾商业观察》施子夫 黄懿 2024年6月24日&#xff0c;厦门东昂科技股份有限公司&#xff08;以下简称&#xff0c;东昂科技&#xff09;在北交所网站披露第二轮审核问询函的回复。自2024年1月IPO申请获北交所受理以来&#xff0c;东昂科技已经收到北交所下发的两轮审核问询…

探索SoMeLVLM:面向社交媒体处理的大型视觉语言模型

SoMeLVLM: A Large Vision Language Model for Social Media Processing 论文地址: https://arxiv.org/abs/2402.13022https://arxiv.org/abs/2402.13022发表在ACL 2024 1.概述 在线社交媒体平台涌现出海量的文本与视觉内容,深刻揭示了人们如何交流、互动以及自我表达。随着通…

动态规划数字三角形模型——AcWing 275. 传纸条

动态规划数字三角形模型 定义 动态规划数字三角形模型是在一个三角形的数阵中&#xff0c;通过一定规则找到从顶部到底部的最优路径或最优值。 运用情况 通常用于解决具有递推关系、需要在不同路径中做出选择以达到最优结果的问题。比如计算最短路径、最大和等。 计算其他…

研究上百个小时,高手总结了这份 DALL-E 3 人物连续性公式(下)

根据前两篇学习&#xff0c;如何创建人物连续性公式&#xff0c;或多或少都会联想到 Midjourney 里面的 Seed 值&#xff0c;是否能运用到 Dall e3 里面&#xff0c;那么今天这篇文章更新来了&#xff01;&#xff01; 继续感谢这位伟大的作者&#xff1a;AshutoshShrivastava…

五大基于Cesium的开源框架及其优劣势,一文导读。

2024-03-12 10:49贝格前端工场 OpenGL基础上有了webGL&#xff0c;webGL基础上有了Cesium&#xff0c;Cesium基础上有了N多开源框架&#xff0c;本文带大家看一下。 1. CesiumJS CesiumJS 是 Cesium 引擎的核心框架&#xff0c;提供了丰富的 API 和组件&#xff0c;用于构建…

基于Java微信小程序校园订餐系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

大模型训练十大戒律!!

1.切勿微调&#xff08;Thou Shalt Not Fine-Tune&#xff09;&#xff1a;尽量写prompt&#xff0c;利用大模型本身的能力zeroshot&#xff0c;必要时辅以少量样本&#xff08;few-shot examples&#xff09;或检索增强生成&#xff08;RAG&#xff09;。微调成本高、速度慢且…

如何在web页面下做自动化测试?

自动化测试是在软件开发中非常重要的一环&#xff0c;它可以提高测试效率并减少错误率。在web页面下进行自动化测试&#xff0c;可以帮助我们验证网页的功能和交互&#xff0c;并确保它们在不同浏览器和平台上的一致性。本文将从零开始&#xff0c;详细介绍如何在web页面下进行…

充电宝哪个牌子最好最耐用?耐用西圣、罗马仕、绿联充电宝实测

目前充电宝是我们出行必备的“能量伴侣”。然而&#xff0c;市面上充电宝品牌繁多&#xff0c;让人眼花缭乱&#xff0c;究竟哪个牌子最好最耐用呢&#xff1f;为了给大家找到答案&#xff0c;我们精心挑选了西圣、罗马仕和绿联这三个备受关注的品牌&#xff0c;并对它们的充电…

小米6款手机霸榜618 Top20,看安卓巨头如何撼动苹果地位

618购物节&#xff0c;作为中国电商领域的一大盛事&#xff0c;每年都会吸引无数消费者的眼球。在这场购物狂欢中&#xff0c;智能手机市场的竞争尤为激烈。 今年618&#xff0c;小米以6款手机上榜累计销量TOP20&#xff0c;超越了苹果的5款&#xff0c;成为上榜机型最多的品牌…

74. UE5 RPG 搭建场景设置光照和纹理流送

前面&#xff0c;我们对角色和敌人进行了一些完善。在这一篇文章里面&#xff0c;我们将进行对场景进行搭建&#xff0c;并对场景的光照和场景的后处理进行设置。 创建新场景 选择新建关卡 接着选择将关卡另存为 选择一个合理的位置 我们将场景内的网格地面删除掉&#xf…

如何提高测试管理的效率和一致性?

TestComplete 是一款自动化UI测试工具&#xff0c;这款工具目前在全球范围内被广泛应用于进行桌面、移动和Web应用的自动化测试。 TestComplete 集成了一种精心设计的自动化引擎&#xff0c;可以自动记录和回放用户的操作&#xff0c;方便用户进行UI&#xff08;用户界面&…

什么是 Linux 内核,其功能是什么?

inux内核是Linux操作系统的核心组件&#xff0c;负责管理系统的硬件资源&#xff0c;并为应用程序提供基本的操作系统服务。刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Linux的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后…

服务器神秘挂起:一场惊心动魄的内核探案

2024年6月17日&#xff0c;我们的运维团队突然收到了一连串的告警。监控大屏上&#xff0c;代表着不同 Sealos 可用区的绿点中&#xff0c;零星地闪烁起了一两个红点。 “奇怪&#xff0c;怎么有几台服务器突然 hang 住了&#xff1f;” 值班的小辉皱起了眉头。 这次故障的诡…

能运行的Bug就别动它了...程序员老梗图什么时候看见才能不笑啊

说到程序员&#xff0c;那可是外界眼中自带光环的生物——掌控代码的大神&#xff0c;改变世界的王者&#xff01; 然而&#xff0c;现实却是“甲方虐我千百遍&#xff0c;我待bug如初恋”。活多钱少压力大&#xff0c;程序员们只能踏上了自黑、自嘲的不归路&#xff0c;毕竟&…