Thread类的介绍

线程是操作系统中的概念,操作系统中的内核实现了线程这种机制,同时,操作系统也提供了一些关于线程的API让程序员来创建和使用线程。

在JAVA中,Thread类就可以被视为是对操作系统中提供一些关于线程的API的的进一步的封装。

多线程程序的特点

1.每一个线程都是一个执行流

2.CPU对线程的执行是并发执行,同时对线程的执行也是随机调度的。

1.创建线程

1. 通过Thread类创建线程

class  MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread t= new MyThread();t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

我们通过创建一个Thread类的子类MyThread,重写Thread类中的run方法,并通过MyThread子类来创建一个线程的实例化对象,run方法是线程的入口函数。而在主函数中的start方法,表示创建一个线程,且一个线程只能start一次。 

当我们运行上面代码,发现打印的内容的顺序会变换,这是因为多线程的影响,上面代码中有两个线程,分别位t线程和主线程,由于线程在CPU上是并发执行的,且又因为CPU对线程又是随机调度的,这就导致我们两个线程的执行顺序会改变,自然而然打印的顺序也会改变。

2.通过Runnable接口来创建线程

class MyRunnable implements Runnable{@Overridepublic void run() {while (true){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Demo2 {public static void main(String[] args) {Runnable runnable=new MyRunnable();Thread t=new Thread(runnable);t.start();}
}

3. 通过匿名内部类来创建线程

匿名内部类的写法本质完成了3件事

1.创建了一个Thread的子类,不知到该子类的名字,所以为匿名。

2.{ }代码块里面可以编写子类的定义代码,需要哪些属性,需要重写哪些父类的方法等等。

3. 创建了匿名内部类的实例,并将该实例传给了t1.

3.1 Thread
    public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(){@Overridepublic void run() {while(true){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t1.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}
3.2 Runnable接口
public static void main(String[] args) throws InterruptedException {Runnable runnable=new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread t=new Thread(runnable);t.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}

4.通过Lambda表达式式创建线程-----  ()->{}

由于Java中,方法必须依附于类的体系中,由于Lambda表达式本质就是一个回调函数,所以,为了快速实现这个回调函数,在Java中,就创建了一个函数式接口----- ()->{ },{ }代码块可以写上回调函数所需要功能的代码。

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

2.Thread类及常见方法

Thread类是JVM用来管理线程的一个类,也就是说,每个线程都有唯一的Thread类对象与之相关联。在Java中,每一个执行流(线程)都要用一个对象来表示,而Thread类对象就可以用来表示一个线程执行流,JVM会将这些Thread类对象组织起来,进行线程调度和线程管理。

2.1 Thread类常见的构造方法

方法

说明

Thread()创建线程对象
Thread(Runnable target)

使用Runnable对象创建线程对象

Thread(String name)创建线程对象,并给线程命名
Thread(Runnable target,String name)使用Runna对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target)

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

注释:关于线程的名字,我们可以通过Jconsole来观察。

2.2 Thread的几个常见属性

属性获取方法
IDgetID()
名称getName()
状态getState()
优先级getPriority()
是否为后台线程isDaemon()
是否存活

isAlive()

是否被中断isInterrupted()

1.ID是线程的唯一标识,不同线程的ID不会重复

2.名称是各种调试工具用到

3.状态表示线程当前所处的一个状态

4.优先级高的线程理论上更容易被CPU调度到

5.关于后台线程:线程分为后台线程(守护线程)和前台线程(用户线程)。我们需要记住一点:JVM会在一个线程中的所有的前台线程结束之后,JVM才会选择退出(程序终止执行)。也就是说,后台线程的存在不会阻止JVM的终止,相反,前台线程的存在会阻止JVM的终止,即使主线程已经结束。在Java中,线程在默认情况下是后台线程,例如垃圾回收机制就是一种后台线程。但是我们可以通过调用setDaemon(true)方法将一个线程设置为后台线程。

6.是否存活,可以简单理解为run方法是否运行结束。

2.3 如何启动一个线程----start()

一个线程对象被创建出来并不意味着线程就开始运行了。我们需要去调用start方法,接着线程中的run方法就会自动执行,只有run方法执行以后,一个线程才是真正运行起来。而调用了start方法,才是真正的在操作系统的层面建立了一个线程。

如下图:

2.4 中断一个线程

中断一个线程就是让该线程直接停止掉,不会再回复。中断一个线程就是让该线程的run方法(入口方法)尽快结束掉

目前常见终止线程的方式有以下两种方式:

1.通过共享的标记来沟通

2.调用interrupt()方法来通知

1.通过共享的标记来沟通

该方法是通过设计一个外部成员变量来终止一个线程。

如以下代码

public class Demo9 {public static boolean isFinished=false;//共享标记public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!isFinished){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();Thread.sleep(3000);System.out.println("main尝试终止线程t");isFinished=true;}
}

我们通过isFinished 这个外部成员变量的值来通知操作系统是否终止t线程。

运行结果

注意事项:我们不能将该外部成员变量设置成一个局部变量 。否则会报出以下错误

原因解释:

这就涉及到变量捕获的问题 。简单来说,就是lambda表达式希望可以使用外面的变量,触发“变量捕获”的语法。由于lambda是一个回调函数,执行时机是很久以后,当操作系统真正创建出线程之后,才会执行。很有可能在创建线程的过程中,main线程就已经运行结束了,自然而然,身为局部变量的isFinished就会被销毁了。

为了解决这个问题,Java中的做法是,将被捕获的变量拷贝一份到lambda里面,外面变量的是否销毁,就不影响lambda里面的执行了。拷贝就以为着该变量就不适合被修改,尽管你在外面修改了这一变量的值,也不会影响拷贝到内部的值(本质上,被拷贝的变量和拷贝的变量是两个变量)

这种一边变,一边不变,可能给程序员带来更多的疑惑。所以在Java中就规定了,拷贝的变量就压根不允许被改变。所以后面修改isFinished的值时会报一个isFinished should be final or effectively final 的错,因为在Java中,被final修饰的变量是无法被修改的。

那为什么将isFinished设置为成员变量会没事呢?

因为当isFinished是一个成员变量时,此时触发的语法不在是“变量捕获”,而是切换成“内部类访问外部类的成员”的语法。

那是lambda表达式本质上是一个函数式接口,也相当于是一个内部类。isFinished本身就是一个外部类成员,内部类本来就能够访问外部类的成员。由于成员变量的生命周期是让GC(垃圾回收)来管理的,GC可以自动识别不在被引用的的对象,并将其占用的内存空间释放掉。所以在lambda里面就不必担心变量生命周期失效的问题,也就不必拷贝,也就不必有被final修饰的限制。

2.使用interrupt()方法来通知

Java的Thread类中提供了现成的方法直接进行判定线程方法是否被终止,不需要我们自己在创建了。

方法说明

public void interrupt()

中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置中断标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位,不建议使用,静态方法位所有线程共有的
public boolean isInterrupted()判断对象关联的线程标志位是否设置,调用后不清除标志位

thread收到通知的方式有两种:

1.如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruedException异常的形式通知,清楚中断标志 ,当出现InterruedException的时候,要不要结束进程取决于catch中代码的写法可以忽略该异常,也可以跳出循环结束进程。

2.否则,只是内部的一个中断标志位被设置了,可以通过isInterrupted()方法判断中断标志是否被设置,不清除中断标志,这种方法通知收到的更及时,即使线程在sleep也可以马上收到。

public class Demo10 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//报异常,清楚中断标志//break;//跳出循环,结束进程//啥都不写,忽略异常,进程继续执行}}});t.start();Thread.sleep(3000);System.out.println("main尝试终止线程t");t.interrupt();}
}

注意事项:针对上述代码由于每次循环,线程大部分时间在处sleep状态,当主线程调用interrup()方法时,会大概率唤醒sleep方法, sleep方法就会报InterruedException异常。

正常来说,调用interrupt()方法就会将isInterrupted()方法内部的标志位改为true,但是上述代码,能够把sleep()方法唤醒,sleep方法在唤醒之后就会将isInterrupted()方法内部的标志位的值重新改为false。因此在进程不结束的情况下,如果继续执行到循环的条件判断,就会发现能够继续执行循环中的代码。

2.5 等待一个线程-----join()

有时我们需要等到一个线程完成它的所有操作后,才进行下一步操作。这时候就用到了join()方法。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等待millis毫秒
public void jooin(long millis,int nanos)同理当精度更高
public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for(int i=0;i<3;i++){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();System.out.println("main线程在等t线程结束");t.join();System.out.println("t线程结束,main线程结束");}
}

上面的代码中,主线程中线程对象t调用了join方法,这就意味着main线程必须要先等待t线程结束后,才会执行main线程里面的内容。 

运行代码

 

如果调用的是join(long millis)版本,则会表示main线程只会等待t线程millis秒,不管t线程在这段时间内是否终止,main线程不会在等待t线程,而是继续执行自己的任务。 

public class Demo11 {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{for(int i=0;i<3;i++){System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();System.out.println("main线程在等t2秒");t.join(2000);System.out.println("2秒内t线程没有结束,main线程不在等待,执行main线程");}
}
 

 2.6 获取当前线程引用

方法说明
public static ThreadcurrentThread()返回当前线程对象的引用

2.7 休眠当前线程-----sleep()

方法说明
public static void sleep(long millis)休眠当前线程millis毫秒
public static void sleep(long millis,int nanos)可以获得更高精度的睡眠

 关于sleep方法,有一点我们需要记得,因为线程调度是不可控的,所以,sleep方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。

 这是因为调用sleep方法,相当于让当前线程,让出cpu的资源。后续休眠时间结束的时候,该线程需要操作系统内核时,操作系统会将该线程重新调度到cpu上,该线程才能继续执行。

换言之,sleep的时间到了,意味着该线程可以被调度,而不是立即执行,所以实际休眠时间会大于等于参数设置的休眠时间。

特殊用法:sleep(0)

sleep(0)意味着让当前线程立刻放弃CPU资源,等待操作系统重新调度。

 3.线程状态

从操作系统的角度来看,进程的有就绪和阻塞的两种状态。

Java线程也是对操作系统中的线程的重新封装。所以,针对线程的状态,Java中也重新进行了封装。

状态说明
NEWThread对象已经创建,但是start()方法没有被调用
TerminatedThread对象还在,但是内核中线程已经结束
Runnable就绪状态,线程正在CPU上执行或者线程可以随时去CPU上执行
TIME_WAITING线程阻塞,阻塞的时间有上限。一般是由于join(时间),sleep(时间)产生的阻塞。
WAITING死等,没事时间限制的等待。一般是由于join(),wait()产生的阻塞
BLOCKED由于锁竞争产生的阻塞

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

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

相关文章

PHP(一)从入门到放弃

参考文献&#xff1a;https://www.php.net/manual/zh/introduction.php PHP 是什么&#xff1f; PHP&#xff08;“PHP: Hypertext Preprocessor”&#xff0c;超文本预处理器的字母缩写&#xff09;是一种被广泛应用的开放源代码的多用途脚本语言&#xff0c;它可嵌入到 HTML…

从新手到高手:Spring AOP的进阶指南

目录 一、AOP简介 1.1 AOP入门案例 1.2 AOP 优点 二、核心概念 2.1 切面(Aspect) 2.2 切点(PointCut) 2.3 通知(Advice) 2.4 织入(Weaving) 三、AOP 原理 3.1 CGLIB 与 JDK动态代理对比 3.2 切面优先级 四、总结 一、AOP简介 AOP(Aspect-Oriented Programming) 面向切面编…

在各大媒体报纸上刊登自己的文章用什么投稿方法发表快?

在职场中,信息宣传是每个单位的重要工作,而每个月的考核投稿任务更是让我深感压力。作为一名普通员工,我常常面临着如何在各大媒体上顺利发表文章的问题。起初,我选择了传统的邮箱投稿方式,然而这条路却让我陷入了无尽的焦虑和挫败之中。 刚开始投稿时,我满怀激情,认真撰写每一…

[论文笔记]HERMES 3 TECHNICAL REPORT

引言 今天带来论文HERMES 3 TECHNICAL REPORT&#xff0c;这篇论文提出了一个强大的工具调用模型&#xff0c;包含了训练方案介绍。同时提出了一个函数调用标准。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 聊天模…

数据库事务

为了保证一致性 1.ACID 事务具有四个基本特性&#xff0c;也就是通常所说的 ACID 特性&#xff0c;即原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xff08;Durability&#x…

算法: 模拟题目练习

文章目录 模拟替换所有的问号提莫攻击Z 字形变换外观数列数青蛙 总结 模拟 替换所有的问号 按照题目的要求写代码即可~ public String modifyString(String ss) {int n ss.length();if (n 1) {return "a";}char[] s ss.toCharArray();for (int i 0; i < n; i…

使用Python和Proxy302代理IP高效采集Bing图片

目录 项目背景一、项目准备环境配置 二、爬虫设计与实现爬虫设计思路目标网站分析数据获取流程 代码实现1. 初始化爬虫类&#xff08;BingImageSpider&#xff09;2. 创建存储文件夹3. 获取图像链接4. 下载图片5. 使用Proxy302代理IP6. 主运行函数 运行截图 三、总结 项目背景 …

SpringMVC一个拦截器和文件上传下载的完整程序代码示例以及IDEA2024部署报错 找不到此 Web 模块的 out\artifacts\..问题

一、SpringMVC一个拦截器和文件上传下载的完整程序代码示例 本文章是一个 SpringMVC拦 截器和文件上传下载的完整程序代码示例&#xff0c;使用的开发工具是 IntelliJ IDEA 2024.1.6 (Ultimate Edition)&#xff0c; 开发环境是 OpenJDK-21 java version 21.0.2。Tomcatt版本为…

【C++篇】类与对象的秘密(上)

目录 引言 一、类的定义 1.1类定义的基本格式 1.2 成员命名规范 1.3 class与struct的区别 1.4 访问限定符 1.5 类的作用域 二、实例化 2.1 类的实例化 2.2 对象的大小与内存对齐 三、this 指针 3.1 this指针的基本用法 3.2 为什么需要this指针&#xff1f; 3.3 t…

基于SSM+微信小程序的房屋租赁管理系统(房屋2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的房屋租赁管理系统实现了有管理员、中介和用户。 1、管理员功能有&#xff0c;个人中心&#xff0c;用户管理&#xff0c;中介管理&#xff0c;房屋信息管理&#xff…

Java基础-IO基础

IO是指input/output&#xff0c;即输入和输出。输入和输出是以内存为中心的&#xff1a; input 从外部往内存输入数据&#xff0c;比如硬盘中的数据写入内存等。 output 从内存往外输出数据&#xff0c;比如内存数据写入硬盘等。 File File类表示一个文件或者一个目录。使用F…

【服务器虚拟化是什么?】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

父子元素中只有子元素设置margin-bottom的问题

问题代码如下所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>.div1 {background-color: red;width: 80px;height: 80px;border: 1px solid orange;}.div2 {bac…

【飞腾加固服务器】全国产化解决方案:飞腾FT2000+/64核,赋能关键任务保驾护航

在信息安全和自主可控的时代背景下&#xff0c;国产化设备的需求与日俱增&#xff0c;尤其是在国防、航空航天、能源和其他关键行业。高可靠性和极端环境设计的国产加固服务器&#xff0c;搭载强大的飞腾FT2000/64核处理器&#xff0c;全面满足国产自主可控的严苛要求。 性能强…

光伏电站设计之辐照度效果(threejs实现)

类似 solaredge里面的日照度效果 1、由经纬度和屋顶朝向获取&#xff08;参考pvlib&#xff09;当前地区的辐照度值&#xff0c; 2、根据辐照度值插值获取对应辐照度的颜色。 3、计算片段着色器里面计算每个顶点的遮挡率和紫色混合 4、计算鼠标移动中的投射屋顶位置辐照度&…

Ansible自动化运维管理工具

一、Ansible 1.1、自动化运维管理工具有哪些&#xff1f; 工具架构语言使用情况Ansible无clientpython 协议用ssh95%puppetC/Sruby 协议用http基本不用chefC/Sruby 协议用http基本不用saltstackC/Spython 协议用ssh5% 1.2、Ansible简介 Ansible是一个基于Py…

网易翻译工具解析!这几大翻译器值得一试!

翻译工具的出现&#xff0c;使得跨语言沟通变得更加便捷。本文将为您推荐几款优秀的翻译工具&#xff0c;包括福昕在线翻译、福昕翻译客户端、海鲸AI翻译和网易有道翻译&#xff0c;帮助您在学习、工作和生活中轻松应对语言挑战。 福昕在线翻译 直达链接&#xff08;复制到浏…

c4d渲染和3d渲染有什么区别?c4d和3dmax哪个容易学?

在现代设计和创意产业中&#xff0c;3D渲染技术是不可或缺的一部分。它能够帮助设计师和艺术家将他们的创意转化为逼真的视觉效果&#xff0c;从而更好地展示和传达他们的想法。在众多3D渲染软件中&#xff0c;C4D渲染和3D Max是两款备受关注的软件。 本文将探讨C4D渲染和3D渲…

深度学习领域,你心目中 idea 最惊艳的论文是哪篇?

深度学习发展至今&#xff0c;共经历了三次浪潮&#xff0c;20 世纪40年代到60年代深度学习的雏形出现在控制论(cybernetics)中&#xff0c;20 世纪 80 年代 到 90 年代深度学习表现为 联结主义(connectionism)&#xff0c;直到 2006 年&#xff0c;才真正以深度学习之名复兴。…

Android中的内容提供者

目录 1.创建内容提供者 1--手动创建一个Android应用程序 2--创建自定义的内容提供者 2.访问其他应用程序 1. 解析URI 2. 查询数据 3. 遍历查询结果 3)案例:读取手机通信录 1.声明权限 2.activity_main.xml文件内容 3.my_phone_list.xml文件内容 4.定义PhoneInfo实体 5.定义MyPh…