【JavaEE初阶】Thread 类及常见方法、线程的状态

目录

1、Thread 类及常见方法

1.1 Thread 的常见构造方法

1.2 Thread 的几个常见属性

1.3 启动⼀个线程 - start()  

1.4 中断⼀个线程  

 1.5 等待⼀个线程 - join()

1.6 获取当前线程引用

1.7 休眠当前线程

2、线程的状态 

2.1 观察线程的所有状态 

2.2 线程状态和状态转移的意义 

2.3 观察线程的状态和转移


1、Thread 类及常见方法

Thread 类是 JVM 用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。
每个执行流,也需要有⼀个对象来描述,类似下图所示,Thread 类的对象 就是用来描述⼀个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

1.1 Thread 的常见构造方法

 

代码示例:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

1.2 Thread 的几个常见属性

 

  • ID 是线程的唯⼀标识,不同线程不会重复,是JVM自动分配的身份标识
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的⼀个情况,下面我们会进⼀步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。

补充:

前台线程的运行会阻止进程的结束;后台线程的运行不会阻止进程的结束。

咱们代码创建的线程,默认就是前台线程,会阻止进程的结束,只要前台线程没执行完,进程就不会结束,即使main已经执行完毕了。

代码举例:

public class ThreadDemo {public static void main(String[] args) {Thread t= new Thread(()->{for (int i = 0;i < 10;i++) {System.out.println("线程工作");try{Thread.sleep(3000);}catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}

分析这个代码,当执行到t.start()时,会创建一个新的线程,新的线程去执行循环,而main线程继续自己的后续代码的执行,此时后面已没有代码,则main线程执行完毕,可以通过jonsole工具进行查看,如图:

按照我们之前的理解,main执行完毕,进程就应该结束,但是很明显,该进程依然继续执行,我们可以根据上述代码的运行结果来看:

若我们把 t 线程设为后台线程,结果又是如何呢?

设为后台线程的代码:

public class ThreadDemo {public static void main(String[] args) {Thread t= new Thread(()->{for (int i = 0;i < 10;i++) {System.out.println("线程工作");try{Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}});t.setDaemon(true);t.start();}
}

这时的运行结果如图,可知当线程 t 设为后台线程就不会阻止进程的结束了,当main执行完毕,进程就直接结束了。

  • 是否存活,即简单的理解为 run 方法是否运行结束了

补充:

isAlive()该方法表示了内核中的线程(PCB)是否还存在。

java代码中定义的线程对象(Thread)实例,虽然表示一个线程,但这个对象本身的生命周期与内核中的线程PCB生命周期是不完全一样的。

  1. 当实例化完一个对象 t 时,此时 t 对象有了,但内核pcb还没有创建,所以此时isAlive()是false的。
  2. 当执行完 t.start() ,这时就真正在内核中创建出pcb,此时isAlive()的值就是true了。
  3. 当线程run执行完了,此时内核中的线程就结束了(内核pcb就释放了),但是t对象可能还存在,isAlive()的值仍是false。
  • 线程的中断问题,下面我们进⼀步说明

下面是代码示例:

public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName()+ ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName()+ ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName()+ ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName()+ ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName()+ ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName()+ ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());}
}

运行结果:

1.3 启动⼀个线程 - start()  

之前我们已经看到了如何通过覆写 run 方法创建⼀个线程对象,但线程对象被创建出来并不意味着线 程就开始运行了。
  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start()方法,就是喊⼀声:“行动起来!”,线程才真正独立去执行了

 

调用 start 方法, 才真的在操作系统的底层创建出⼀个线程.  对于同一个Thread对象来说,start只能调用一次。

经典面试题:start 和 run 的区别

用一个代码来说明:

class MyThread8 extends Thread {@Overridepublic void run() {while (true) {System.out.println("hello");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}
public class ThreadDemo8 {public static void main(String[] args) {Thread t = new MyThread8();//t.run();   //此时是main方法调用run,没有创建新线程,后续的循环执行不到t.start();  //会创建新的线程,新线程执行run的循环,主线程main继续后续代码while (true) {System.out.println("hello main");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}}
}

当创建一个Thread类对象 t 时:

由 t 调用run方法时( t.run() ),并没有创建出一个新的线程,这个操作还是在主线程main中进行的,循环打印hello,此时代码就只能停留在run的循环中了,下方main中的循环执行不到。

若由 t 调用start,这时会创建出一个新的线程,去执行run循环;main线程则继续执行自己的后续循环。

总结:

作用功能不同:

  1. run方法的作用是描述线程具体要执行的任务;
  2. start方法的作用是真正的去申请系统线程

运行结果不同:

  1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
  2. start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。

1.4 中断⼀个线程  

接着上面图片张三、李四的例子,李四⼀旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加⼀些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停⽌转账,那张三该如何通知李四停止呢?这就涉及到我们的停⽌线程的方式了。

目前常见的有以下两种方式:
  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

示例1: 使用自定义的变量来作为标志位,示例代码:

public class ThreadDemo9 {private static boolean isQuit = false;public static void main(String[] args) {Thread t = new Thread(()->{while (!isQuit) {System.out.println("hello");try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t结束");});t.start();try {Thread.sleep(3000);}catch (InterruptedException e) {e.printStackTrace();}System.out.println("让t线程结束");isQuit = true;}
}

运行结果:

我们可以看到代码中,将自定义的变量标志位写成了类的静态成员变量,那是否可以写为main方法中的局部变量?

不可以! 当你定义为局部变量时,会发现提示编译错误。

这是因为我们使用了创建线程的lambda表达式方法,lambda表达式有一个语法:变量捕获。

lambda表达式的变量捕获,本质上就是,把外面的变量当作参数传进来(参数是隐藏的)。这个捕获的变量得是 final 修饰的或者“事实final”(虽然没有写final,但是没有修改)。因为此处isQuit是确实要修改的,不能写成final,它也不是事实final,因此将标志位变量isQuit定义为局部变量是行不通的!

那为什么可以定义为成员变量呢?

lambda表达式,本质上是“函数式接口”,匿名内部类。对于内部类,访问外部类的成员是完全可以的,这个事情不受到变量捕获的影响。

示例2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位。
Thread 内部包含了⼀个 boolean 类型的变量作为线程是否被中断的标记。

示例代码:

public static void main(String[] args) {Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("我是一个线程,正在工作");//interrupt会影响sleep,线程不会结束try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程结束");});t.start();try {Thread.sleep(3000);}catch (InterruptedException e) {e.printStackTrace();}System.out.println("让t线程结束");t.interrupt();}
}

 运行结果:

可以看到抛出异常,并且线程不会结束,继续往下循环输出。这是因为什么呢?

这是因为存在sleep,在执行sleep的过程中,调用interrupt。大概率sleep休眠时间还没到,被提前唤醒了。

sleep被提前唤醒,会做两件事:

  1. 抛出InterruptedException,紧接着就会被catch捕获到
  2. 清除Thread对象的isInterrupted标志位

对于线程不会结束,就是标志位被清除了。我们通过interrupt方法,已经把标志位设为true了,但是sleep提前唤醒操作,又把标志位清除,设为原来的false,所以线程不会结束。

如何解决呢?

要想线程结束,只需要在catch中加上break即可。但此时仍会抛异常,不想抛,就不写输出e.printStackTrace()。

Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("我是一个线程,正在工作");try {Thread.sleep(1000);}catch (InterruptedException e) {// e.printStackTrace();break;  //加上break,仍会抛异常(不想抛,就不写上面输出e.printStackTrace()),线程会结束}}System.out.println("线程执行完毕");});

这时的结果:

thread 收到通知的方式有两种:
  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,并清除中断标志当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法,可以选择忽略这个异常,也可以跳出循环结束线程。
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过 Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志。这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

 1.5 等待⼀个线程 - join()

有时,我们需要等待⼀个线程完成它的⼯作后,才能进行自己的下⼀步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要⼀个方法明确等待线程的结束。

代码示例:

public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还在⼯作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!")};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始⼯作");thread1.start();thread1.join();System.out.println("李四⼯作结束了,让王五开始⼯作");thread2.start();thread2.join();System.out.println("王五⼯作结束了");}
}

 这里是几个join方法:

1.6 获取当前线程引用

 

public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

1.7 休眠当前线程

也是我们比较熟悉⼀组方法,有⼀点要记得,因为线程的调度是不可控的,所以,这个方法只能保证 实际休眠时间是大于等于参数设置的休眠时间的。

 

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

2、线程的状态 

2.1 观察线程的所有状态 

 线程的状态是⼀个枚举类型 Thread.State.

public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}
  • NEW: 安排了工作, 还未开始行动
  • RUNNABLE: 可工作的,又可以分成正在⼯作中和即将开始⼯作.
  • BLOCKED: 这几个都表示排队等着其他事情
  • WAITING: 这几个都表示排队等着其他事情
  • TIMED_WAITING: 这几个都表示排队等着其他事情
  • TERMINATED: 工作完成了.

2.2 线程状态和状态转移的意义 

大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。  

还是我们之前的例子:
刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;
当李四、王五开始去窗口排队,等待服务,就进⼊到 RUNNABLE 状态。该状态并不表示已经被银行 工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
当李四、王五因为⼀些事情需要去忙,例如需要填写信息、回家取证件、发呆⼀会等等时,进⼊
BLOCKED WATING TIMED_WAITING 状态;
如果李四、王五已经忙完,为 TERMINATED 状态。
所以,我们上面内容介绍的 isAlive() 方法,可以认为是 处于不是 NEW 和 TERMINATED 的状态都是活着的。

2.3 观察线程的状态和转移

 观察 1: 关注 NEW RUNNABLE TERMINATED 状态的转换

public class ThreadStateTransfer {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());;}System.out.println(t.getName() + ": " + t.getState());;}
}

观察 2: 关注 WAITING BLOCKED TIMED_WAITING 状态的转换

public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();
}
使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED
修改上面的代码, 把 t1 中的 sleep 换成 wait
public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {// [修改这⾥就可以了!!!!!]// Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, "t1");...}

使用户jconsole 可以看到 t1 的状态是 WAITING。

结论:
  • BLOCKED 表示等待获取锁,WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.
  • TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒

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

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

相关文章

硬盘上不小心删除了重要文档?试试这6个成功率高的数据恢复工具!

您是否不小心重新格式化了存储卡或删除了想要保留的照片&#xff1f;最好的照片恢复软件可以提供帮助&#xff01;如果您使用数码相机拍摄的时间足够长&#xff0c;那么当您错误地删除了想要保留的图像、重新格式化了错误的 SD 卡&#xff0c;或者发现您的珍贵照片由于某种莫名…

CFO的GenAI应用指南

CFO应该采取的最重要行动是识别最大的创造价值的机会&#xff0c;然后确保他们获得所需的资金和其他资源。GenAI拥有成为革命性技术的潜力&#xff0c;但它不会改变金融和经济学的基本原则&#xff1a;一家公司必须产生高于资本成本的回报。 技术改变了每一项业务&#xff0c;往…

分布式锁,分布式锁应该具备哪些条件,分布式锁的实现方式有:基于Zookeeper实现、Redis实现、数据库实现

文章目录 分布式锁0-1分布式锁--包含CAP理论模型概述分布式锁&#xff1a;分布式锁应该具备哪些条件&#xff1a;分布式锁的业务场景&#xff1a; 分布式锁的实现方式有&#xff1a;基于Zookeeper - 分布式锁实现思想优缺点基于Redis - 分布式锁实现思想实现思想的具体步骤&…

【LeetCode 热题 HOT 100】题解笔记 —— Day01

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

企业海外分部,如何实现安全稳定的跨境网络互连?

如今&#xff0c;众多企业广泛采取数字化业务系统&#xff0c;如OA、ERP及CRM等&#xff0c;来提升其业务运营效率。同时&#xff0c;私有云与公有云混合架构也逐渐普及化。 具体来说&#xff0c;很多企业选择将研发系统部署在公司本地的私有云环境&#xff0c;以此确保数据安全…

首批!创邻科技入选《图数据库金融应用场景优秀案例》

11月11日&#xff0c;“全球金融科技中心网络年会”在第三届全球金融科技大会暨第五届成方金融科技论坛上成功在京举办。会上&#xff0c;北京前沿金融监管科技研究院发布了基于国际标准组织——国际关联数据基准委员会&#xff08;LDBC&#xff09;的《图数据库金融应用场景优…

路由器DHCP分配IP地址规则

路由器DHCP分配IP地址的机制&#xff1a; 先设置一个IP地址池&#xff0c;假设是192.168.1.100-192.168.1.199一共100个。 来一个请求&#xff0c;看一下是不是以前请求过的地址&#xff0c;如果是&#xff0c;还是返回以前给过的IP&#xff0c;然后将到期时间(有些路由器默认…

【Python3】【力扣题】349. 两个数组的交集

【力扣题】题目描述&#xff1a; 【Python3】代码&#xff1a; 1、解题思路&#xff1a;集合的交集。两个数组都转为集合&#xff0c;获取集合的交集。 知识点&#xff1a;set(...)&#xff1a;转为集合&#xff0c;集合的元素不重复。 集合1.intersection(集合2)&#xff1a…

Python3.7 win7系统安装openCV方案

为了使用机房电脑处理数字图像问题&#xff0c;在win7系统安装了python opencv, 测试使用的是官网下载python3.7.7版本&#xff0c;如果官网安装&#xff0c;直接安装即可 pip install python-opencv 这样会自动安装对应版本的numpy 如果官网安装很慢&#xff0c;想使用镜像安…

【Linux】EVIOCGBIT

EVIOCGBIT(ev, len) 该怎么理解&#xff1f; 我们可以推断出&#xff0c;它是一个宏&#xff0c;它的前两个参数已经确定了&#xff0c;具体的功能由后两个参数(ev,len)来决定。Linux-4.9.88\include\uapi\linux\input.h #define EVIOCGBIT(ev,len) _IOC(_IOC_READ, E, 0x20 …

如何申请文心一言文心千帆大模型API调用资格、获取access_token,并使用SpringBoot接入文心一言API

前段时间&#xff0c;百度文心一言&文心千帆大模型开放了API调用的测试&#xff0c;接下来&#xff0c;教大家申请测试资格并接入文心千帆大模型的API。 一、文心一言&文心千帆的测试资格申请 1. 确保拥有一个百度智能云的账号 右上角点击注册&#xff0c;内容如实填…

【知网稳定检索】第九届社会科学与经济发展国际学术会议 (ICSSED 2024)

第九届社会科学与经济发展国际学术会议 (ICSSED 2024) 2024 9th International Conference on Social Sciences and Economic Development 第九届社会科学与经济发展国际学术会议(ICSSED 2024)定于2024年3月22-24日在中国北京隆重举行。会议主要围绕社会科学与经济发展等研究…

postgresql从入门到精通 - 第35讲:中间件PgBouncer部署|PostgreSQL教程

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第35讲&#…

荆涛《春节回家》:歌声中的年味与乡愁

荆涛《春节回家》&#xff1a;歌声中的年味与乡愁春节&#xff0c;对于每一个中国人来说&#xff0c;都是一年中最为重要的时刻。它不仅仅是一个节日&#xff0c;更是团圆、乡愁、回忆与希望的象征。歌手荆涛的歌曲《春节回家》恰恰捕捉到了这些情感&#xff0c;用音乐为人们绘…

hdlbits系列verilog解答(Exams/m2014 q4e)-46

文章目录 一、问题描述二、verilog源码三、仿真结果 一、问题描述 实现以下电路&#xff1a; 二、verilog源码 module top_module (input in1,input in2,output out);assign out ~(in1 | in2);endmodule三、仿真结果 转载请注明出处&#xff01;

【SpringBoot系列】SpringBoot日志配置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

hdlbits系列verilog解答(exams/m2014_q4i)-45

文章目录 一、问题描述二、verilog源码三、仿真结果 一、问题描述 实现以下电路&#xff1a; 二、verilog源码 module top_module (output out);assign out 1b0;endmodule三、仿真结果 转载请注明出处&#xff01;

【C++】哈希(位图、布隆过滤器)

一、哈希的应用&#xff08;位图和布隆过滤器&#xff09; 1、位图&#xff08;bitset&#xff09; &#xff08;1&#xff09;位图概念 【题目】 给 40亿 个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这 40亿 个数中。…

mysql 行转列 GROUP_CONCAT 试验

1.概要 很多时候需要用到行专列的方式做数据分析。比如对通讯数据的采集 数据采集结果如下&#xff1a; 变量值采集周期131251132272 我想要看的结果 变量1变量2采集周期351372 就是我想看到相关数据的周期变化情况。 2.试验 2.1创建数据如下&#xff08;表名 tb5&…

发现有一个会Python的男友魅力值杠杠的!!!

Python能做什么&#xff1f; 可以做日常任务&#xff0c;比如自动备份你的MP3&#xff0c;可以做网站&#xff0c;很多著名的网站像知乎、YouTube就是Python写的&#xff0c; 可以做网络游戏的后台&#xff0c;很多在线游戏的后台都是Python开发的。 上面说的这些本人并没有实…