【JavaEE】浅谈线程(二)

线程

线程的常见属性

线程属性可以通过下面的表格查看。
•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");
    }

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

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

报道 | 2024年7月-2024年9月国际运筹优化会议汇总

封面图来源&#xff1a; https://www.pexels.com/zh-cn/photo/1181406/ 2024年7月-2024年9月召开会议汇总&#xff1a; 2024 INFORMS Advances in Decision Analysis Conference (ADA) Location: Finland Important Dates: Conference: July 10-12, 2024 Details:https://w…

【学习】科大睿智解读ITSS认证中咨询机构的作用

企业拥有ITSS认证这不仅将为企业开拓商机&#xff0c;提升竞争力&#xff0c;还能促使企业改进内部运维流程&#xff0c;提高服务质量&#xff0c;为客户提供更优质的IT运维支持。在ITSS认证中&#xff0c;咨询机构扮演着重要的角色&#xff0c;其主要作用包括以下几个方面&…

【服务器】磁盘满载--docker 的日志文件太大造成满载

一.背景 早上过来测试反馈服务器都宕机了,访问不了。一看服务器磁盘都已经满了。所以开始清磁盘数据。 二.解决 主要查看下面目录情况: /home/libe/docker /containers /volumes /overlay21.查看磁盘情况 df -h/ du -a|sort -rn|…

前端开发的工厂设计模式

在前端开发中&#xff0c;工厂设计模式&#xff08;Factory Pattern&#xff09;是一种非常有用的设计模式&#xff0c;能够帮助我们在创建对象时减少代码的重复性和复杂性。 一、工厂设计模式概述 工厂设计模式是一种创建型设计模式&#xff0c;主要目的是定义一个用于创建对…

【PL理论深化】(6) Ocaml 语言: 函数 | 匿名函数 | 函数可以接受多个参数 | OCaml 是一种将函数视为 first-class 的语言

&#x1f4ac; 写在前面&#xff1a;本章我们继续介绍如何使用 OCaml 进行函数式编程。介绍如何使用 let 定义函数&#xff0c;讲解匿名函数&#xff0c;函数可以接受多个参数&#xff0c;以及讨论 OCaml 将函数视为 first-class。 目录 0x00 函数 0x01 匿名函数&#xff08…

【C语言】--常见类型和概念

❤️个人主页: 起名字真南 &#x1f495;个人专栏:【数据结构初阶】 【C语言】 目录 第一个C语言程序main函数printf函数库函数关键字字符和ASCII码字符串和\0转义字符 第一个C语言程序 #include<stdio.h> int main() {printf("Hello World\n");return 0; }ma…

【神经网络】CNN网络:深入理解卷积神经网络

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&#xff01; CNN网络&#xff1a;深入理解…

springboot集成达梦数据库,打包后,tomcat中启动报错

背景&#xff1a;springboot集成达梦数据库8&#xff0c;在工具idea中正常使用&#xff0c;但是打包后&#xff0c;无法启动&#xff0c;报错 pom引入的依赖 但是这种情况&#xff0c;只有在idea中启动没问题的解决方法 需要修改引入的依赖&#xff0c;再次打包就可以 <d…

PatchMixer:一种用于长时间序列预测的Patch混合架构

前言 《PatchMixer: A Patch-Mixing Architecture for Long-Term Time Series Forecasting》原文地址&#xff0c;Github开源代码地址GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文&#xff0c;并对其进行了中文翻译…

苹果电脑压缩pdf文件,苹果电脑里如何压缩pdf文件

压缩PDF文件是现代办公和日常生活中经常需要处理的一项任务&#xff0c;无论是为了节省存储空间、方便网络传输&#xff0c;还是为了在移动设备上更流畅地阅读文档&#xff0c;学会有效地压缩PDF都显得尤为重要。在本文中&#xff0c;我们将详细探讨压缩PDF的方法&#xff0c;从…

Mac安装多版本node

Mac下使用n模块去安装多个指定版本的Node.js&#xff0c;并使用命令随时切换。 node中的n模块是&#xff0c;node专门用来管理node版本的模块&#xff0c;可以进行node版本的切换&#xff0c;下载&#xff0c;安装。 1.安装n npm install -g n 2.查看版本 n --version 3.展…

LeetCode题练习与总结:随机链表的复制--138

一、题目描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的…

IT入门知识第八部分《云计算》(8/10)

目录 云计算&#xff1a;现代技术的新篇章 1. 云计算基础 1.1 云计算的起源和发展 云计算的早期概念 云计算的发展历程 1.2 云计算的核心特点 按需自助服务 广泛的网络访问 资源池化 快速弹性 按使用量付费 1.3 云计算的优势和挑战 成本效益 灵活性和可扩展性 维…

日语培训日语等级考试柯桥小语种学习语言学校

什么是外来语 外来语是指在日本的国语中使用的来源于外国语言的词汇。但狭义上的外来语则是指来源于欧美国家语言的词汇&#xff0c;其中大部分是来源于英美语系的词汇。日语中的汉语词汇很多&#xff0c;大多是自古以来从中国引进的&#xff0c;从外来语的定义看&#xff0c;汉…

运算符重载详解(完全版)

1.运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其返回值类型&#xff0c;函数名字和参数列表&#xff0c;其返回值类型与参数列表都与普通的函数类似 函数名&#xff1a;关键字operator后面接需要重载的…

记因hive配置文件参数运用不当导致 sqoop MySQL导入数据到hive 失败的案例

sqoop MySQL导入数据到hive报错 ERROR tool.ImportTool: Encountered IOException running import job: java.io.IOException: Hive exited with status 64 报错解释&#xff1a; 这个错误表明Sqoop在尝试导入数据到Hive时遇到了问题&#xff0c;导致Hive进程异常退出。状态码…

Lua网站开发之文件表单上传

这个代码示例演示如何上传文件或图片&#xff0c;获取上传信息及保存文件到本地。 local fw require("fastweb") local request require("fastweb.request") local response require("fastweb.response") local cjson require("cjson&q…

windterm多窗口同时操作多台服务器

在配置k8s等多服务器环境时&#xff0c;我们要对多台服务器进行相同的操作&#xff0c;使用多窗口同步输入实现一次命令多段执行 初始配置&#xff0c;服务器都已连好 分屏窗口&#xff0c;按下altw->alth水平分屏&#xff0c;按下altw->altv,垂直分屏 按下ctrlshiftm,…

SAP消息号 VF028

客户在VF11冲销发票之后&#xff0c;没有生成正式的财务凭证&#xff0c;然后VF02的时候出现如下报错&#xff1a; “自动清算出具发票凭证XXXXXXX&#xff08;被冲销凭证号&#xff09;且不可能取消凭证XXXXXXX&#xff08;冲销凭证号&#xff09; 原因&#xff1a;销售订单2…

小程序下拉刷新,加载更多数据,移动端分页

文章目录 页面结构图WXML页面代码js代码wxss代码总结备注 参考&#xff1a;https://juejin.cn/post/7222855604406796346 页面结构图 一般页面就4个结构&#xff1a;最外滚动层、数据展示层、暂无数据层、没有更多数据层。 如图&#xff1a; WXML页面代码 <scroll-view …