java线程之Thread类的基本用法
创始人
2025-05-31 02:03:13

Thread类的基本用法

  • 1. Thread类的构造方法
  • 2. Thread的几个常见属性
    • 常见属性
    • 线程中断
    • 等待一个线程

小鱼在上一篇博客详细的讲解了如何创建线程,java使用Thread类来创建多线程,但是对于好多没有相关经验的人来说,比较不容易理解的地方在于操作系统调度的执行过程. 我们通过下面代码举例:

    public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello t");});//调用start(),创建一个新的线程t.start();System.out.println("hello main");}

我们的java在启动时,会创建一个主线程,此时主线程的名字就是main,接下来我们执行main线程里面的一条条代码了,当我们执行到t.start()时,系统就会自动帮我们创建一个新的线程,并且会自动帮我们调用该线程中的run()方法,此时的run()方法就可以认为是个入口,一些重要的实现逻辑将都在里面实现.

我们通过上述代码举例:

当我们实例一个thread并且调用start()方法之后, main 线程和 t 线程就是(并发+并行)的过程,由于多线程有一个万恶之源–抢占机制,所以就会导致我们无法预测到程序是优先执行"hello t" 还是 “hello main”,并且对于谁先结束线程的任务,我们也是不确定的,可能是main线程优先结束,也可能是t线程优先结束.

在这里插入图片描述

创建线程是看start的顺序,但是具体的线程对应的任务什么时候执行,要看系统调度器。

1. Thread类的构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象并命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

关于构造方法的讲解在这篇文章中详细的讲到了,包括如何创建一个线程,以及创建线程需要注意的事项都在这里一一列举了出来多线程的创建及其注意事项

2. Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

常见属性

(1) getid() : ID表示线程的身份.就类似于我们的身份证一样,是独一无二的.这里的方法获取到的是线程在JVM中的身份标识,线程的标识有好多个,在内核的PCb上有标识,在用户态线程数据库中也有标识(操作系统的线程库)

(2) getName() : 获取当前线程的名字,多用于调试时使用.

(3) getState() :获取当前线程的状态,状态表示线程当前所属的一个情况.

(4) getPriority() : 优先级高的线程理论上来说更容易被调度到,但实际上并没有什么区别.大概可以理解为,你建议我不要晚睡,但也仅仅是建议,我可以不听.

(5) isDaemon() :daemon称为"守护线程",也可以称为后台线程,如果是后台线程就会返回true,前台线程则是false,我们可以这么理解后台线程,当我们手机打开qq这个app时,此时qq就是前台运行,但是由于我们后续可能会切换到快手app,此时qq就来到后台运行了.

一个线程创建出现默认是前台线程,前台线程会阻止进程的退出,进程只有在所有的前台线程都执行完之后才可以退出,但是对于后台进程,并不会阻止进程的结束.

JVM会在一个进程的所有非后台进程结束后,才会结束运行。

(6)isAlive() : 是否存活,即run()方法是否结束。

public class Demo4 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t 线程正在运行");}});System.out.println("t线程是否存活 "+t.isAlive());t.start();System.out.println("t线程是否存活 "+t.isAlive());try {Thread.sleep(1000);System.out.println("t线程是否存活 "+t.isAlive());} catch (InterruptedException e) {e.printStackTrace();}}
}

如果 t 的run还没跑,isAlive就为false;
如果 t 的run正在跑,isAlive就为true;
如果 t 的run跑完了,isAlive就为false;

执行结果:

在这里插入图片描述

此时我们将上述涉及到的属性的状态打印出来:

在这里插入图片描述

使用setDaemon(布尔值)方法设置一个线程为后台,true表示后台线程。

class MyThread1 extends Thread{@Overridepublic void run() {System.out.println("hello  t");}
}
public class Demo1 {public static void main(String[] args) {Thread t = new MyThread1();t.start();System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//设置为后台线程t.setDaemon(true);System.out.println("是否是后台线程 "+ t.isDaemon());}
}

执行结果:

在这里插入图片描述
上述操作都是获取到的一瞬间的状态,不是持续的状态。

start()和run()方法的区别:

当我们调用start()这个方法时,创建一个新的线程(有一个向系统创建一个线程的这么一个动作),此时这个线程会由系统自动调用run()方法(入口方法).并且这个run()方法是在这个新建的线程中运行的.
但是如果是程序员手动调用run()方法的话,并不会新建一个线程,并且该方法是在调用这个方法的线程中执行的.

获取当前线程的引用:

方法说明
public static Thread currentThread();返回当前线程对象的引用
 public static void main(String[] args) {//获取当前线程的引用Thread t = Thread.currentThread();//通过当前线程的引用调用当前线程的名字System.out.println("当前线程名字 : "+t.getName());}

执行结果:
在这里插入图片描述

休眠当前线程:

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠
   public static void main(String[] args) {Thread t = new Thread(()->{System.out.println("hello t");});t.start();try {//睡眠1000ms,也可以称为阻塞1000msThread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello main");}

睡眠的状态就相当于这个线程被阻塞了,阻塞的时间由里面的参数决定,在这个线程阻塞的时间并不影响其他线程执行,可以理解为~我们在打游戏,打着打着未成年时间限制弹了出来,这时候就要求我们停止打游戏这个行为,只有等这个等待时间过去之后才可以继续打游戏.

:等待的过程中什么都不能做,相当于被定身了一样!!!

线程中断

中断线程,就是让线程尽快的把入口方法(run方法)执行结束。有2种方法。

(1) 直接手动创建标志位来区分线程是否要结束.

public class ThreadDemo5 {static boolean flog = true;public static void main(String[] args)  {Thread t = new Thread(()->{while (flog){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程运行中~~");}System.out.println("t 线程结束");});t.start();try {//睡眠3000msThread.sleep(3000);//将标志位设置为false终止上面的while()System.out.println("控制新线程结束");flog = false;} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果:

在这里插入图片描述
有些线程可能会存在一些循环需要执行的情况,但是可能只是需要执行一定时间,并不是一直执行,所以就可以通过sleep()来控制这个线程结束的时间.

需要注意的是:flog需要是成员变量,局部变量是线程私有的,其余线程不能进行修改,但是成员变量是处于进程的数据区,是共享的资源,所以可以访问和修改~~

Thread内置了标志位,不需要手动创建标志位。

(2)Thread内置的标志位

public class ThreadDemo5 {static boolean flog = true;public static void main(String[] args)  {Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程运行中~~");}System.out.println("t 线程结束");});t.start();try {//睡眠3000msThread.sleep(3000);//将标志位设置为false终止上面的while()System.out.println("控制新线程结束");t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}
}

在这里插入图片描述
Thread.currentThread().这个是Thread类的静态方法,通过这个方法可以获取当前线程,类似this;

isInterrupted()方法是一个判定内置的标志位,默认是false,true表示线程要中断。现在运行,查看结果。

在这里插入图片描述

为什么在抛出异常后线程还是在不停的执行呢?

那是因为当代码执行到interrupt()时会做两件事:

(1) 如果t 线程没有处于阻塞状态时,这时候interrupt()就会修改内置标志位,将isInterrupted的状态设置为true;

(2) 如果 t 线程处于阻塞状态时,此时interrupt()会通过触发异常,将阻塞状态(sleep)提前唤醒,但是由于把sleep唤醒会导致标志位变成false,也就是将标志位清空,如果标志位本身就是false则不变.由于我们的循环条件是while(!Thread.currentThread().isInterrupted()),又因为我们的标志位又变成了false,所以我们可以继续执行这个循环.

实际中能产生阻塞的方法有很多,并不是只有sleep,这里只是用sleep举例子.

由于计算机的执行速度时很快的,所以阻塞状态大概占线程执行状态的99.99%,所以会有更大的几率触发(2).

当然,即使是大部分时间都在阻塞状态,但是该线程始终是在阻塞状态和正常运行的状态之间切换.

当然,我们可以通过break关键字,当抛出异常时直接break就可以不再执行循环,之后就可以结束线程,这就相当于我正在打游戏,我妈喊我吃饭,此时我不敢抗拒,只能去吃饭.

代码如下:

public class ThreadDemo5 {public static void main(String[] args)  {Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();break;}System.out.println("线程运行中~~");}System.out.println("t 线程结束");});t.start();try {//睡眠3000msThread.sleep(3000);//将标志位设置为false终止上面的while()System.out.println("控制新线程结束");t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}
}

此时的运行结果:

在这里插入图片描述
当然,由于我们捕捉到了异常,我们不仅可以直接让他break,还可以让他先等待一会再结束执行.

就像我们在打电话一样:

比如我正在打游戏,女朋友让我出去买菜,此时我不搭理她,这个时候就相当于,你说你的我干我的.

但是呢?过了一会女朋友又来跟我说让我买菜去,我因为怕她生气嘛,所以我跟她说,五分钟,五分钟就好…等五分钟一到,我就退出游戏买菜去了.

当我买完菜的时候,刚躺到床上打开游戏,此时厨房传来女朋友的尖叫声,那我必须立刻放下手机去看看发生了什么事情…

总结三种状态;(1)立即退出 (2) 等待一会再退出 (3) 不退出,你说你的我做我的.

由我们自己来决定什么时候退出.

如果是直接写死的话,让我收到命令就必须退出,假如我在跟老板汇报工作,此时女朋友让我去买菜,如果我把老板电话挂掉,那自己也就寄了.

只有自己才有资格决定自己要做什么事情,别人的话之能当作意见,只有自己是为自己好.

等待一个线程

线程是一个随机调度过程,等待线程,就是控制两个线程的结束顺序.

但是呢?我们可以通过过join()方法来控制线程的执行顺序.

大家看下面的代码以及执行结果:

在这里插入图片描述
由于线程是抢占式执行的,所以可能这次是先打印 main 但是下次可能就先打印t了,可是程序员不喜欢这种不确定性,所以我们可以通过join()方法来控制顺序.

大家看下面的代码和执行结果:

在这里插入图片描述
当我们在 main 线程中调用 t.join() ,此时只有当 t 线程执行完之后,才会去执行 main 线程中的内容,我们也可以理解为,在哪个线程中调用join()方法,那么就哪个线程阻塞,只有当调用这个方法的引用所对应的线程执行完之后,才可以继续执行后续代码~

在这里插入图片描述
当然,这个join()也是可以传参数的,假如 t 线程一直没有执行完,那么我这个线程就不工作了? 就像我约女神见面一样,我等了一天,两天,三天都没等到,难道我还要等一辈子? 所以此时我们就可以传一个参数,作为最大等待时间,超过这个时间我就不等了,世界上美女那么多,我换个人舔还不行?

join方法自身也会阻塞,Java线程但凡是阻塞的方法都可能抛出这个异常(throws InterruptedException)。

join有两种行为:

(1)如果被等待的线程还没有执行结束,那么直接阻塞等待;

例如:我接我孩子放学,他5.00放学,我4.50到了,此时我就需要等待他放学,菜才能回家.

(2)如果被等待的线程已经执行结束了,就直接返回。

例如:我接我孩子放学,他5.00放学,我5.10才到,那么我就不用等,可以直接接回家.

join方法不带参数的时候,就是死等,一直等待下去。方法带参数的时候,如果超过了等待的最大时间,就开始执行下一步的代码。一般写带参数的join方法。

相关内容

热门资讯

Mysql常用数据类型总结 整形 枚举类型ENUE整形       TINYINT,SMALLINT,MEDIUMINT,IN...
【flink sql】创建表 flink sql创建表语法 CREATE TABLE [IF NOT EXISTS] [catal...
python opencv 保... 👨‍💻个人简介: 深度学习图像领域工作者 dz...
Pytorch深度学习实战3-... 目录1 数据集Dataset2 数据加载DataLoader3 常用预处理方法4 模型处理5 实例&...
自定义类型的超详细讲解ᵎᵎ了解...   目录 1.结构体的声明 1.1基础知识 1.2结构体的声明 1.3结构体的特殊声明  1.4结构...
Docker等容器技术如何与移... 移动应用程序的开发面临着很多挑战,包括开发环境的设置、测试的困难、部署的复杂性等。由于...
【微服务】—— Nacos安装... 文章目录1. Windows安装1.1 下载安装包1.2 解压1.3 端口配置1.4 启动1.5 访...
【OpenGL】 为了理解这个函数我们需要先学习一些OpenGL的内容 OpenGL可视化 https://g...
hjr-详细说一下Redis集... Redis作用 缓存 一般我们用Redis做缓存,热点数据 击穿:访问到...
【蓝桥杯】 C++ 数字三角形... 文章目录题目描述输入描述输出描述实现代码解题思路注意点知识点 题目描述 上图给出了一个数字三角形。从...
VR全景展会丨探索未来,重塑现... 随着科技的不断发展,虚拟现实(VR)技术逐渐成为一个重要的...
C++数据类型 目录 C++基础数据类型 指针 指针类型 指针赋值 引用 参考:《深...
超实用!!! 三分钟将你的项目... 文章目录前言一、在项目中新增配置二、配置github page setting?三、如...
数据结构---队列 专栏:数据结构 个人主页:HaiFan. 专栏简介:这里是...
数字操作方法 系列文章目录 前端系列文章——传送门 JavaScript系列文章——传送门 文章目录系列文章目录...
Cartesi 2023 年 ... 查看 Cartesi Machine、Cartesi Rollups 和 Noether 的更新正在...
JavaWeb——jsp概述入... JSP定义:  在如下一个jsp文件里面有如下的代码  <%@ page content...
一切喜怒哀乐都来自于你的认知 01 有个学子,准备出国,父母请来清华的教授宁向东。请问教授࿱...
JAVA并发编程——synch... 引言         Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,...
git学习----3.21 未... 文章目录前言Git :一个分布式版本控制工具目标一、概述1.1 开发中的实际场景1.2...
Qt优秀开源项目之十七:QtP... QtPromise是Promises/A+规范的Qt/C++实现。该规范的译...
【前端八股文】JavaScri... 文章目录Set概念与arr的比较属性和方法并集、交集、差集Map概念属性和方法String用索引值和...
海康硬盘录像机接入RTSP/o... EasyNVR安防视频云服务平台可支持设备通过RTSP/Onvif协议接入平台,能提供...
在混合劳动力时代如何避免网络安... 在混合劳动力时代如何避免安全网络风险 三年多来,混合工作一直是工作生活中不可或缺的一...
2023还不懂Jmeter接口... 这里介绍的Jmeter接口测试的的实战,如果文章内容没遇看懂的话,我这边...
基于4G/5G弱网聚合的多链路... 基于4G/5G多卡聚合(弱网聚合)的智能融合通信设备技术亮点 增强带宽提供可靠连接 通过将多个有线和...
如何使用Synplify综合v... 文章目录使用Synplify综合的好处synplify的教程方法1(无效)...
2023年全国最新高校辅导员精... 百分百题库提供高校辅导员考试试题、辅导员考试预测题、高校辅导员考试真题、辅导员证考试题库等ÿ...
2022年18个值得期待的Le... 有数百个独特的LearnDash附加组件,您可能很难选择您的LearnDash LMS...
【java基础】Stream流... 文章目录基本介绍流的创建流的各种常见操作forEach方法filter方法map方法peek方法fl...