【JavaEE】_多线程Thread类及其常用方法

目录

1. Thread类常用构造方法

2. Thread类的几个常见属性

3. 启动一个线程

4. 中断一个线程

4.1 方法1:手动设置标志位

4.2 方法2:使用Thread内置的标志位

5. 等待一个线程

6. 获取当前线程引用

7. 休眠当前线程


1. Thread类常用构造方法

方法

说明

Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象并命名
Thread(Runnable target,String name)使用Runnable对象创建线程对象并命名
Thread(ThreadGroup group,Runnable target)使用线程组对线程分组管理

前两个构造方法前文已经使用,此处不再赘述,此处主要展示带有命名的线程对象的创建:

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

运行代码并根据jdk安装路径打开bin文件找到jconcole.exe文件:

打开在本地进程中连接对应进程:

进入界面选择进程后,就可以看到正在运行的线程,点击线程还可以显示线程的执行位置:

注:(1)给Thread命名仅便于调试时对线程进行区分,对于线程执行没有其他影响;

(2)java进程启动后不只有我们手动编写的线程,还有一些JVM自己创建的线程用于其他的不同工作,比如收集统计调试信息,监听网络链接等等;

2. Thread类的几个常见属性

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

注:(1)ID是线程的唯一标识,不同线程不会重复;

(2)获得的名称也就是构造时指定的名称,主要用于调试时便于查看线程状态;

(3)状态表示当前线程所处的状态,后续详细介绍;

(4)优先级高的线程理论上来说更容易被调度到;

(5)后台线程:

①  如果一个线程是后台线程,就不影响进程退出,main方法执行完毕后就会结束进程,并强行终止后台线程;

如果一个线程是前台线程,即使main方法执行完毕,也必须等前台线程执行完毕1才能结束进程,JVM会在一个进程的所有非后台线程结束后才会结束运行

②  代码里手动创建的线程(包括main线程)默认是前台线程,其他的jvm自带的线程都是后台线程,也可以使用setDaemon将一个前台线程设置为后台线程;

③ 注意区别线程调度与后台线程;

(6)是否存活可以理解为:操作系统中对应的线程是否正在运行;

Thread t 对象的生命周期和内核中对应的线程生命周期并不完全一致,t对象被创建后,在调用start方法之前,系统中是没有对应线程的,在run方法执行完毕后,系统中的线程就销毁了,但是t对象还可能存在;

在调用start之后,run执行完之前,isAlive就是返回true,

如果是调用start之前,run执行完之后,isAlive就是返回false;

(7)线程的中断问题,后续详细介绍;

3. 启动一个线程

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

运行以上代码,截取部分输出结果如下:

将上文代码的t.start()语句更换为t.run()语句再试运行,截取部分输出结果如下:

因为run方法只是一个普通的方法,在main线程中调用run,其实并没有创建心得线程,上文中的循环语句依然是在main线程中执行的,在一个线程中,代码就会按照从前至后的顺序进行运行,此时就只会在第一个循环结构中一直打印Hello Thread.;

可以通过修改循环条件进行验证:

public class Demo2 {public static void main(String[] args) {Thread t = 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);}}});t.run();for(int i=0;i<2;i++){System.out.println("Hello Main.");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

再运行代码,输出结果为:

  

注:run方法只是一个遵循串型执行的、描述任务内容的普通调用方法;

调用start方法,才会在操作系统的底层创建一个线程;

4. 中断一个线程

中断一个线程的关键在于使当前线程对应的run方法执行完

但对于特殊的main线程来说,需要main方法执行完线程才完;

4.1 方法1:手动设置标志位

此处的标志位是自己创建的变量,来控制线程是否要执行结束:

// 通过设置标志位中断一个线程
public class Demo2 {private static boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(flag){System.out.println("Hello Thread.");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();//在主线程中通过更改flag变量的取值来操作t线程是否结束//当flag为假时不再进入循环Thread.sleep(3000);flag = false;}
}

输出结果为:

注:(1)t线程开始执行后,当3s时间截止后,标志位flag被修改,此时t线程run方法所在循环被终止,t线程终止,再后的main方法也执行完毕,main线程也终止,同时进程也终止了;

(2)此处因为多个线程共用一个虚拟地址空间,因此main修改的flag与t线程判定的flag是同一个值;

(3)以上写法并不严谨,后续再进行介绍,同时由于手动设置标志位来中断一个线程不能及时响应,尤其是在sleep休眠时间比较久时,故而引出中断一个线程的第二种方法;

4.2 方法2:使用Thread内置的标志位

 判断线程是否被中断:Thread.currentThread().isInterrupted()实例方法,其中currentThread可以获取到当前线程的对象的引用;

②  设置线程中断位置:t.interrupt()

(更推荐② 一个代码中的线程可能有很多,随时哪个线程都有可能终止,①方法表示一个程序只有一个标志位,但是②方法判定的标志位是Thread的普通成员,每个实例都有自己的标志位;)

代码示例1:

public class Demo4 {public static void main(String[] args) {Thread t = new Thread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("Hello Thread.");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();//在主线程中调用interrupt方法来中断t线程try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//中断t线程t.interrupt();}
}

运行结果如下:

 注:(1)调用interrupt()方法可能产生两种情况:

如果调用线程处于就绪状态,则是设置线程标志位为true;

如果调用线程处于阻塞状态(sleep),interrupt方法就会强行打破休眠,则sleep会触发InterruptException异常,导致线程从阻塞状态被唤醒,将线程从sleep中被唤醒时,又会将原先设定的标志位再设置回false,即清空了标志位;

不只是sleep方法可以将线程意外中断后清除标志位,像join、wait等可以造成线程堵塞的方法都有类似于清除标志位的设定;

(2)上文代码一旦出发了异常就会进入catch语句,在catch语句中,就打印出当前异常位置的代码调用栈,打印完毕后继续运行;

这显然不是我们期待的结果,故而需要修改:

代码示例2:

public class Demo3 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("Hello Thread.");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//触发异常后稍后几秒再终止t线程try{Thread.sleep(1000);}catch(InterruptedException ex){ex.printStackTrace();}//触发异常后立刻退出循环break;}}});t.start();Thread.sleep(3000);t.interrupt();     //通知终止t线程}
}

在t线程抛出Interrupted异常后立刻停止工作,输出结果为:

也可看出interrupt唤醒sleep中的线程后,要将标志位重新置回的意义:

t.interrupt()只是main线程通知t线程终止,而t线程是否终止取决于其本身,重新将标志位置回意味着可以在t线程内部选择继续执行、终止、稍后终止,否则t线程只能立刻终止。

5. 等待一个线程

多个线程之间的调度顺序是不确定的,线程之间的执行顺序是调度器无序、随机执行的,有时我们需要控制线程之间的执行顺序,线程等待就是控制线程结束先后顺序的重要方法,哪个线程调用join()方法,哪个线程就会阻塞等待对应线程的run方法执行结束为止。

代码示例1:当被等待的线程尚未执行完毕时:

public class Demo4 {public static void main(String[] args) {Thread t =  new Thread(()->{for(int i=0;i<3;i++){System.out.println("Hello Thread.");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();System.out.println("Hello Main");//让main线程等待t线程的run方法执行完毕try {t.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hello Main");}
}

输出结果为:

注:(1)上文代码是main线程调用join()方法,是针对t这个线程对象调用的,此时就是让main等待t,调用join()方法后,main线程就会进入阻塞状态,直到t的run()方法执行完毕后,main线程才会继续执行; 

(2)干预两个线程的执行顺序体现在:通过线程等待控制先让t结束,main后结束;

(3)注意区别干预线程执行顺序与优先级的区别:优先级是操作系统内部内核进行线程调度使用的参考量,在用户层面代码不能完全干预或控制,线程执行顺序是代码中控制的先后顺序;

代码示例2:当被等待的线程已经执行完毕时:

public class Demo4 {public static void main(String[] args) {Thread t =  new Thread(()->{for(int i=0;i<3;i++){System.out.println("Hello Thread.");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hello Main");//让main线程等待t线程的run方法执行完毕try {t.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hello Main");}
}

输出结果为:

如果被等待的线程在其他线程尚未执行到等待该线程的语句时该线程已经执行完毕,则join直接返回;

注:join()操作默认情况下是持续等待的,为了避免这种机制带来的麻烦,join提供了另外一个版本,可以设定最长等待时间(等待时间上限),具体内容请看示例3:

代码示例3:指定join的等待上限:

public class Demo4 {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();}}});t.start();System.out.println("Hello Main");//让main线程等待t线程的run方法执行完毕try {t.join(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Hello Main");}
}

输出结果为: 

上文代码t.join(2000)代表:main线程进入阻塞状态等待t线程run方法执行完毕,如果2s之内t线程执行结束了,此时join直接返回,如果3s之后t线程还未结束,join也直接返回;

6. 获取当前线程引用

Thread.currentThread()是一个静态方法,能获取当前线程实例的引用,哪个线程调用则获取哪个线程实例的引用;

代码示例1:通过继承Thread类创建线程:

public class Demo6 {public static void main(String[] args) {Thread t = new Thread("Thread t"){@Overridepublic void run(){System.out.println(Thread.currentThread().getName());}};t.start();//main线程System.out.println(Thread.currentThread().getName());}
}

输出结果为:

由于本方式是通过继承Thread类来创建接口的,故而也可以通过this.getName()方式获取当前类的对象的引用:

public class Demo6 {public static void main(String[] args) {Thread t = new Thread("Thread t"){@Overridepublic void run(){//System.out.println(Thread.currentThread().getName());System.out.println(this.getName());}};t.start();//main线程System.out.println(Thread.currentThread().getName());}
}

代码示例2:通过实现Runnable接口创建接口:

这种方式创建线程,则不能通过this.getName()方法获取当前对象的引用:

Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(this.getName());}});

由于此时this不是指向Thread类型了,而是指向Runnable,Runnable只是一个任务,没有name属性;

只能通过Thread.currentThread().getName()方式获取当前对象的引用:
 

        Thread t = new Thread(new Runnable() {@Overridepublic void run() {Thread.currentThread().setName("Thread t");System.out.println(Thread.currentThread().getName());}});t.start();

输出结果为:

7. 休眠当前线程

在前文已经介绍过:对于一个线程的进程,在系统中是通过PCB描述的,通过双向链表组织的;

对于一个有多个线程的进程,每个线程都有一个PCB,一个进程对应的就是一组PCB,PCB上有一个tgroupId,这个id就相当于是进程的id,同一个进程中的若干个tgroupId是相同的;

PCB即process control block进程控制块,其实在Linux内核是不区分进程与线程的,只有在程序员写应用程序代码时才会进行区分,Linux内核只识别辨认PCB,在内核中线程被称为轻量级进程;

实际在操作系统中调度线程的时候,会从就绪队列中挑选合适的CPB到CPU上运行,而执行了sleep()或join()语句的PCB位于阻塞队列就无法上CPU,只有当休眠结束,再次进入就绪队列才会在后续上CPU被执行;

让线程休眠本质就是让该线程不参与调度了,如令该线程休眠1000ms,其实在该线程从阻塞队列被迁移回就绪队列后并不会被立刻调度,往往令休眠的线程的休眠时间要超过手动设置的时间;

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

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

相关文章

【开源】SpringBoot框架开发食品生产管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 加工厂管理模块2.2 客户管理模块2.3 食品管理模块2.4 生产销售订单管理模块2.5 系统管理模块2.6 其他管理模块 三、系统展示四、核心代码4.1 查询食品4.2 查询加工厂4.3 新增生产订单4.4 新增销售订单4.5 查询客户 五、…

Java面向对象案例之设计用户去ATM机存款取款(三)

需求及思路分析 业务代码需求&#xff1a; 某公司要开发“银行管理系统”&#xff0c;请使用面向对象的思想&#xff0c;设计银行的储户信息&#xff0c;描述存款、取款业务。 储户类的思路分析&#xff1a; 属性&#xff1a;用户姓名、密码、身份证号、账号、帐户余额 方法&a…

【开源】JAVA+Vue.js实现城市桥梁道路管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥梁4.2 新增城市桥梁4.3 编辑城市桥梁4.4 删除城市桥梁4.5 查询单个城市桥梁 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的城市桥梁道路管理系统&#xff0c;支持…

大数据,对于生活的改变

谷歌通过对于疾病的查询量可以预测一个个h1n1病毒的大爆发&#xff0c; 大数据时代对于人的考验 用户的搜索记录就是一种信息&#xff0c;这种信息会满足其基础相关的词条与其有关的词条&#xff08;最为原始的搜索机制&#xff0c;国内的搜索引擎都是采用这种基础原理。&…

Java并发之死锁详解

(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨ &#x1f440;&#x1f440;&#x1f440; 个人博客&#xff1a;小奥的博客 &#x1f44d;&#x1f44d;&#x1f44d;&#xff1a;个人CSDN ⭐️⭐️⭐️&#xff1a;传送门 &#x1f379; 本人24应届生一枚&#xff0c;技术和水平有限&am…

论文精读--对比学习论文综述

InstDisc 提出了个体判别任务&#xff0c;而且利用这个代理任务与NCE Loss去做对比学习从而得到了不错的无监督表征学习的结果&#xff1b;同时提出了别的数据结构——Memory Bank来存储大量负样本&#xff1b;解决如何对特征进行动量式的更新 翻译&#xff1a; 有监督学习的…

线程池再思考(业务学习)

1.为什么要用线程池&#xff1f; **1.降低资源消耗&#xff0c;**复用已创建的线程来降低创建和销毁线程的消耗。 2.提高响应速度&#xff0c;任务到达时&#xff0c;可以不需要等待线程的创建立即执行。 3.提高线程的可管理性&#xff0c;使用线程池能够统一的分配、调优和监…

字符设备驱动分步注册过程实现LED驱动的编写,编写应用程序测试

头文件&#xff01;&#xff01;&#xff01;&#xff01; #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0…

二叉树(4)——链式二叉树

1 二叉树的概念 二叉树是&#xff1a; 空树非空&#xff1a;根节点&#xff0c;根节点的左子树、根节点的右子树组成的。 二叉树定义是递归式的&#xff0c;因此后序基本操作中基本都是按照该概念实现的。 2 二叉树的遍历 2.1 前序、中序以及后序遍历 学习二叉树结构&#xf…

N叉树的前序遍历

1.题目 这道题是2024-2-18的签到题&#xff0c;题目难度为简单。 考察的知识点为DFS算法&#xff08;树的前序遍历&#xff09;。 题目链接&#xff1a;N叉树的前序遍历 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历…

天锐绿盾 | 办公终端文件数据\资料防泄密软件

天锐绿盾是一款电脑文件防泄密软件&#xff0c;旨在帮助企业保护其敏感数据免受未经授权的访问和泄露。该软件采用先进的加密技术和文件监控机制&#xff0c;确保企业数据在存储、传输和使用过程中的安全性。 PC地址&#xff1a;https://isite.baidu.com/site/wjz012xr/2eae091…

华清作业day57

效果图&#xff1a; 头文件&#xff1a; #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #define…