JavaEE-多线程初阶(2)

目录

1.创建线程的五种写法

1.1 继承Thread类

1.2 实现Runnable接口

1.3 使用匿名内部类

1.4 使用Runnable,匿名内部类

1.5 引入lambda表达式

2.Thread类及常见方法

2.1 认识Thread

2.2 Thread的常见构造方法

2.3 Thread的几个常见属性

关于后台线程

关于是否存活isAlive()

线程组ThreadGroup

2.4 启动一个线程 -start()

2.5 中断一个线程

使用自定义变量

变量捕获

 使用Thread自带的属性

2.6 等待一个线程

2.7 获得当前线程的引用

2.8 休眠当前线程:sleep



1.创建线程的五种写法

1.1 继承Thread类

详细见上篇文章

1.2 实现Runnable接口

详细见上篇文章

1.3 使用匿名内部类

创建Thread内部类,并在Thread内部类里面重写run方法:

    public static void main(String[] args) {Thread t=new Thread(){@Overridepublic void run() {System.out.println("Hello Thread");}};t.start();}

【注意】

使用匿名内部类可以少定义一些类(比如上面代码就省去了MyThread类)。

一般如果某个代码是“一次性的”,就可以使用匿名内部类。

1.4 使用Runnable,匿名内部类

创建Runnable内部类,并在其内部重写run方法,最后作为构造方法的参数传入Thread类:

    public static void main(String[] args) {Thread t=new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Hello Thread");}});t.start();}

1.5 引入lambda表达式

针对写法三写法四进一步改进,引入lambda表达式

进入Thread类的定义文件可以发现Thread类是实现了Runnable接口的

再接着进入Runnable接口,会发现Runnable其实是一个函数式接口:

对于函数式接口,可以使用lambda表达式来重写run方法:

    public static void main(String[] args) {Thread t=new Thread(()->System.out.println("Hello Thread"));t.start();}

一般推荐使用lambda表达式的写法。

2.Thread类及常见方法

2.1 认识Thread

Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。
⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,⽽ Thread 类的对象就是⽤来描述⼀个线程执⾏流的,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。

2.2 Thread的常见构造方法

对上述五个构造方法的解释:

1.创建线程对象

2.使用Runnable对象创建线程对象

3.创建线程对象,并命名

4.使用Runnbale对象创建线程对象,并命名

5.线程可以用来分组管理,分好的组即为线程组

对三个线程分别命名为t1,t2,t3:

    public static void main(String[] args) {Thread t1=new Thread(()->{while (true){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t1");Thread t2=new Thread(()->{while (true){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t2");Thread t3=new Thread(()->{while (true){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t3");t1.start();t2.start();t3.start();}

运行代码,并在jconsole中观察线程,会发现t1,t2,t3线程,这三个线程就是上面代码创建出来并重命名的线程:

2.3 Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否为后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
ID 是线程的唯⼀标识,不同线程不会重复
名称是各种调试⼯具⽤到的
状态表示线程当前所处的⼀个情况,后面我们会进⼀步说明
优先级高的线程理论上来说更容易被调度到
关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运⾏。
是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
线程的中断问题,下⾯我们进⼀步说明

关于后台线程

现有两个线程,将t2线程设置为后台线程(也叫守护线程)t1线程运行五秒t2线程死循环,当t1线程结束时,不论t2(后台线程)有没有结束,整个进程都会结束:

    public static void main(String[] args) {Thread t1=new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("Hello t1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1线程结束...");},"t1");Thread t2=new Thread(()->{while (true){System.out.println("Hello t2");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"t2");t2.setDaemon(true);t1.start();t2.start();}

t1这种能影响到进程继续存在的线程就被成为前台线程

一般自己创建的线程(包括main主线程)默认都是前台线程,可以通过setDaemon方法来修改。

使用jconsole观察线程,会发现除了我们自己创建的线程以外,JVM还自带了很多线程:

这些线程都是后台线程,当进程结束了,这些线程也就随之结束了。

JVM提供的这些线程都是属于有特殊功能的线程,会跟随整个进程持续执行的。

比如:垃圾回收线程


关于是否存活isAlive()

JAVA代码中创建的Thread对象,和系统中的线程是一 一对应的关系

但是,Thread对象的生命周期和系统中线程的生命周期是不同的。

(可能存在,Thread对象还存活,但是系统中的线程已经销毁的情况)案例如下:

    public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 3; i++) {try {System.out.println("Hello Thread");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();while(true){System.out.println(t1.isAlive());Thread.sleep(1000);}}


线程组ThreadGroup

Thread(ThreadGroup group, String name)

线程组(不做详细介绍)

就是把多个线程放在一个组里

统一针对这个线程组里的所有线程进行一些属性设置

(比如:统一设置成后台线程)

2.4 启动一个线程 -start()

Java标准库/JVM提供的方法

本质上是调用操作系统的API

每个Thread对象,都是只能start一次的

每次想创建一个新的线程,都得创建一个新的Thread对象(不能重复利用)

如果一个对象多次创建线程(start):

结果是报错:

Java中期望,Thread对象和操作系统中的线程是一一对应

2.5 中断一个线程

中断一个线程,其实就是终止一个线程(该线程以后不会再恢复了

在操作系统中,“中断”一次还有别的含义,不要混淆

使用自定义变量

先看一个案例:

在类里面定义一个成员变量isInterrupted,在main方法里通过改变isInterrupted的值来控制t1线程的终止:

public class Demo3 {public static boolean isInterrupted=false;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{while(!isInterrupted){System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程结束...");});t1.start();for (int i = 0; i < 3; i++) {Thread.sleep(1000);}isInterrupted=true;}
}

如果不用成员变量,而是把isInterrupted定义在main里面(作为局部变量),再执行代码就会报错:

首先解释一下这个变量该如何修改正确:

1.用final修饰isInterrupted变量:

2.变量isInterrupted实际上为final,意思就是:isInterrupted不能被修改

变量捕获

会有这样的现象,是因为:lambda里面希望使用外面的变量,就会触发“变量捕获”

对于“变量捕获”的解释:

1.产生变量捕获的原因

lambda是回调函数,他要在操作系统真正创建出线程之后才会被执行。

因此很有可能会发生:当这个线程刚创建好的时候,main线程就已经结束了,isInterrupted变量也已经被销毁了。

2.“变量捕获”

为了解决上述的问题,Java的做法是,把lambda外面的变量拷贝一份到lambda里面,如此一般外面的变量无论销毁与否,都不会影响到lambda里面的执行。这个过程就是“变量捕获”

3.为什么不能修改变量

拷贝,就是把一个变量的值拷贝给另一个变量,本质上这两个变量是没有关联的。

由于这两个变量没有关联,修改了lambda外面的变量并不会影响到lambda里面的变量。

因此Java大佬干脆压根就不让程序员修改这个变量。

这就是为什么被捕获的变量必须为final或者effectively final

而使用成员变量的isInterrupted可以被修改,是因为:

1.lambda本质上是函数式接口,相当于一个内部类

2.isInterrupted变量是外部类的成员。

3.内部类本来就可以访问外部类的成员

4.成员变量的生命周期是由GC(垃圾回收线程)来管理的。在lambda里不必担心该变量生命周期失效的问题,也就不需要发生“变量捕获”,也就不必限制final之类。

 使用Thread自带的属性

Java的Thread对象中提供了现成的变量,直接进行判定,不需要再自己创建了。

但是由于lambda里的定义是在new Thread之前的,也就是在Thread t声明之前,因此不能直接使用t1:

此时就需要用到获得当前线程引用的方法:Thread.currentThread()

这个方法可以返回当前线程的引用(相当于this):

    public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{while(!Thread.currentThread().isInterrupted()){try {System.out.println("Hello Thread");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程结束...");});t1.start();Thread.sleep(3000);System.out.println("main线程尝试终止t1线程");t1.interrupt();}

执行结果:

执行后系统抛出异常,异常原因:sleep被中断。

解释:

当t1.interrupt()执行时,此时t1线程内大概率还在处于sleep(1000)的状态,interrupted()的执行强行唤醒了sleep,因此就会抛出异常,异常被try catch捕获,然后抛出一个RuntimeException。

将抛出的RuntimeException给改成break就不会抛出异常了,但是sleep被唤醒的异常依旧存在:

为什么要使用break?如果不加break(直接空着),这个线程就会继续死循环执行

原因:

正常来说,调用Interrupt方法就会修改isInterrupted方法内部的标志位,设为true

由于上述代码中,是把sleep给强行唤醒了,这种提前唤醒的情况下,sleep就会在唤醒后,把isInterrupted的标志位设置回false

因此,while循环条件达成,会继续进行死循环执行

至于为什么要这样设计,个人认为是想让程序员拥有更多选择:

程序员可以自行决定,这个线程是要立即结束,要是等会再结束,还是不结束...

2.6 等待一个线程

方法:join()

作用:从此处开始阻塞等待,等到一个线程结束了再继续执行

案例:

    public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 3; i++) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1线程结束...");});t1.start();t1.join();System.out.println("main线程结束...");;}

 ​​​​​​​

执行结果:

但是这样设计,只要线程t1不结束,主线程的join就会一直等待下去,这样并不科学。

因此Java提供了带参数的方法,可以指定超时时间(最大等待时间)

当等待的时间超出了设置好的超时时间,不论t1线程是否结束,main线程都继续执行。

join方法还有带两个参数的版本:

一般不会用到纳秒这个参数:在计算机中,很难进行ns(纳秒)级别的精确时间的测量(误差比较大)。尤其是,线程本身的开销往往就会达到ms级别。

1s=1000ms

1ms=1000ns

1us=1000ns

2.7 获得当前线程的引用

方法:public static Thread currentThread();

作用:返回当前线程对象的引用(这个方法在哪个线程里就返回哪个线程的引用)

    public static void main(String[] args) {Thread t1=new Thread(()->{System.out.println("t1:"+Thread.currentThread().getName());});t1.start();System.out.println("main:"+Thread.currentThread().getName());}

2.8 休眠当前线程:sleep

因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。

语法:

    public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(1000*3);System.out.println(System.currentTimeMillis());}


如果哪里有疑问的话欢迎来评论区指出和讨论,如果觉得文章有价值的话就请给我点个关注还有免费的收藏和赞吧,谢谢大家

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络安全】揭示 Web 缓存污染与欺骗漏洞

未经许可,不得转载。 文章目录 前言污染与欺骗Web 缓存污染 DoS1、HTTP 头部超大 (HHO)2、HTTP 元字符 (HMC)3、HTTP 方法覆盖攻击 (HMO)4、未键入端口5、重定向 DoS6、未键入头部7、Host 头部大小写规范化8、路径规范化9、无效头部 CP-DoS10、HTTP 请求拆分Web 缓存污染与有害…

重工业数字化转型创新实践:某国家特大型钢铁企业如何快速落地基于实时数仓的数据分析平台

使用 TapData&#xff0c;化繁为简&#xff0c;摆脱手动搭建、维护数据管道的诸多烦扰&#xff0c;轻量替代 OGG, Kettle 等同步工具&#xff0c;以及基于 Kafka 的 ETL 解决方案&#xff0c;「CDC 流处理 数据集成」组合拳&#xff0c;加速仓内数据流转&#xff0c;帮助企业…

Golang | Leetcode Golang题解之第522题最长特殊序列II

题目&#xff1a; 题解&#xff1a; func isSubseq(s, t string) bool {ptS : 0for ptT : range t {if s[ptS] t[ptT] {if ptS; ptS len(s) {return true}}}return false }func findLUSlength(strs []string) int {ans : -1 next:for i, s : range strs {for j, t : range s…

(C#面向初学者的 .NET 的生成 AI) 第 1 部分-简介

第 1 部分简介就是由Luis Quintanilla讲述本系列教程要学习哪些部分&#xff0c;基本都是介绍&#xff0c;内容不是很多。但可以先了解一下.net 生成式AI需要学习接触哪些东西。 在这个关于机器学习和AI的初学者系列中&#xff0c;Luis Quintanilla向.net开发人员介绍了基础知识…

【密码学】全同态加密基于多项式环计算的图解

全同态加密方案提供了一种惊人的能力 —— 能够在不知道数据具体内容的情况下对数据进行计算。这使得你可以在保持潜在敏感源数据私密的同时&#xff0c;得出问题的答案。 这篇文章的整体结构包括多项式环相关的数学介绍&#xff0c;基于多项式环的加密和解密是如何工作的&…

Spring Boot中解决BeanDefinitionStoreException问题的实战分享

目录 前言1. 问题背景2. 问题分析2.1 异常分析2.2 常见的错误原因2.3 排查过程 3. 解决方案3.1 清理缓存和重建项目3.1.1 清理IDEA缓存3.1.2 使用Maven清理并重建项目 3.2 升级Maven版本3.2.1 下载最新Maven版本3.2.2 IDEA配置新的Maven版本3.2.3 清理缓存并重新构建 3.3 验证问…

Java避坑案例 - 线程池未复用引发的故障复盘及源码分析

文章目录 问题现象问题定位问题代码根因分析现象剖析newCachedThreadPool 源码SynchronousQueue特点构造方法主要方法应用场景Code Demo运行结果 问题修复 问题现象 时不时有报警提示线程数过多&#xff0c;超过2000 个&#xff0c;收到报警后查看监控发现&#xff0c;瞬时线程…

AHB Matrix 四星级 验证笔记(1.8-1.9)scoreboard的实现

文章目录 前言一、scoreboard接收数据的方式和比较行为的策略选择1.接受数据的方式1. 首先是数据从哪儿来&#xff1f; 2.比较数据的方式1.方案一2.方案二3. 方案的选择 二、scoreboard的实现1.代码 三、tip1.编辑断点2. return3.有关函数的返回值&#xff1a;函数内部隐式声明…

商业潜规则揭秘:从成交艺术到客户满意度的全方位策略

潜规则一&#xff1a;成交的艺术——七大核心原则 顾客追求的是超值感&#xff0c;而非单纯低价。 与顾客讨论的重点应是价值&#xff0c;而非价格。 客户没有绝对的对错&#xff0c;关键在于服务是否到位。 销售方式比销售产品本身更重要。 没有绝对最好的产品&#xff0c;只有…

在IDEA2024中生成SpringBoot模板

1、创建新项目 根据自己想要创建的工程类型选择&#xff0c;这里创建的时web工程 生成项目&#xff1a; 注意&#xff1a;SpringBoot只会扫描主程序所在的包及其下面的子包

物流行业信息化整体规划方案|117页PPT

文件是关于“物流行业信息化整体规划方案”的详细规划报告&#xff0c;涵盖了物流信息化咨询项目的规划报告&#xff0c;包括业务理解与需求分析、信息化现状分析、信息化蓝图规划以及实施路径与保障措施等多个方面。以下是对文档内容的总结&#xff1a; 1. 引言&#xff1a;信…

opencv优秀文章集合

文章目录 一、 CV领域1.1 图像处理1.2 目标检测与识别1.3 图像分割、目标追踪1.4 姿态估计1.5 3D视觉1.6 图像生成1.7 机器视觉1.8 其它 二、 nlp三、语音四、推荐系统 《OpenCV优秀文章集合》《OpenCV系列课程一&#xff1a;图像处理入门&#xff08;读写、拆分合并、变换、注…

Windows转Mac过渡指南

最近由于工作原因开始使用mac电脑&#xff0c;说实话刚拿到手的时候&#xff0c;window党表示真的用不惯。坚持用一下午之后&#xff0c;发现真的yyds&#xff0c;这篇文章说说mac电脑的基本入门指南。 1. 不会使用mac的触摸板&#xff0c;接上鼠标发现滚轮和windows是反的。 …

字符串逆序(c语言)

错误代码 #include<stdio.h>//字符串逆序 void reverse(char arr[], int n) {int j 0;//采用中间值法//访问数组中第一个元素和最后一个元素//交换他们的值&#xff0c;从而完成了字符串逆序//所以这个需要临时变量for (j 0; j < n / 2; j){char temp arr[j];arr[…

安全成为大模型的核心;大模型安全的途径:大模型对齐

目录 安全成为大模型的核心 大模型安全的途径:大模型对齐 人类反馈强化学习(RLHF) 直接偏好优化(DPO) 安全成为大模型的核心 大模型安全的途径:大模型对齐 大模型对齐技术(Alignment Techniques for Large Language Models)是确保大规模语言模型(例如GPT-4)的输…

K8s企业应用之容器化迁移

#作者&#xff1a;曹付江 K8s企业应用之容器化迁移 Kubernetes&#xff08;K8s&#xff09;中的企业应用容器化迁移是一个复杂但重要的过程&#xff0c;平滑的迁移应用&#xff0c;可以让开发、运维、测试人员循序渐进的学习和掌握Kubernetes&#xff0c;通常包括以下步骤&am…

redis详细教程(3.hash和set类型)

hash Redis中的Hash是一种数据结构&#xff0c;用于存储键值对集合。在Redis中&#xff0c;Hash非常适合表示对象&#xff0c;其中对象的每个字段都对应一个键值对。以下是关于Redis中Hash的详细讲解&#xff1a; 特点&#xff1a; 1. 键值对集合&#xff1a;Hash是一个包含…

linux 安装php扩展:xlswriter

这里以xlswriter扩展为例 进入官方扩展&#xff1a;https://pecl.php.net查询自己php对应版本的扩展包 下载扩展 wget https://pecl.php.net/get/xlswriter-1.5.5.tgz 解压扩展 tar -zxvf xlswriter-1.5.5.tgz 进入扩展目录 cd xlswriter-1.5.5 查找对应php版本的phpiz…

SSID,即Service Set Identifier(服务设置的表示符号)

一、什么是SSID&#xff1f; SSID&#xff0c;即Service Set Identifier&#xff0c;是无线网络中的一个标识符&#xff0c;用于区分不同的无线网络。它可以理解为无线网络的名称&#xff0c;当我们在手机或电脑上搜索可用的无线网络时&#xff0c;就是通过SSID来识别和连接的…

LabVIEW过程控制实验平台

A3000实验平台通过LabVIEW开发&#xff0c;实现了过程控制的虚拟仿真与实时通信&#xff0c;显著提高了教学与实验的互动性和效率。该平台采用模块化设计&#xff0c;支持多种控制策略的实验教学&#xff0c;克服了传统实验设备的不足。项目背景 目前高校过程控制实验设备普遍…