java EE初阶 — Thread类及常见方法
创始人
2024-02-21 17:42:41
0

文章目录

    • 1.Thread 常见的构造方法
    • 2.Thread 几个常见的属性
    • 3.启动一个线程 - start()
    • 4.终止一个线程
      • 4.1 使用标志位来控制线程是否要停止
      • 4.2 使用 Thread 自带的标志位来进行判定
    • 5.等待一个线程 - join()
    • 6.获取当前线程引用
    • 7.休眠当前线程

1.Thread 常见的构造方法

  • Thread() - 创建线程对象
  • Thread(Runnable target) - 使用 Runnable 对象创建线程对象
  • Thread(String name) - 创建线程对象,并且命名
  • Thread(Runnable trget, String name) - 使用 Runnable 对象创建线程对象,并且命名
  • Thread(ThreadGroup group, Runnable target) - 线程可以被用来分组管理,分号的即为线程组,目前了解即可

例子:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("名字");
Thread t4 = new Thread(new MyRunnable(), "名字");
package thread;public class ThreadDemo6 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello world");}}}, "MyRunnable");thread.start();}
}

2.Thread 几个常见的属性

1、 ID(getid()) 是线程的唯一标识,不同线程不会重复。


2、名称(getName()) 是构造方法里起的名字。


3、状态(getState()) 表示线程当前所处的一个情况,(java 里的线程状态要比操作系统原生的状态更丰富一些)
下面我们会进一步说明。


4、 优先级(getPriority)可以获取,也可以设置,但是没什么作用。


5、关于
后台线程(isDaemon())
,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

前台线程会阻止进程结束,前台线程工作没做完,进程是是无法结束工作的。
后台线程不会阻止进程的结束,后台线程工作没做完,进程也是可以结束。

代码手动创建线程的时候。默认都是前台
包括 main 默认也是前台的,其他 JVM 自带的都是后台的。

也可以使用setDaemon设置后台线程,也是守护线程。

package thread;public class ThreadDemo7 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello");}}, "MyRunnable");thread.setDaemon(true);thread.start();}
}

把 thread 设置成了后台线程/守护线程,此时进程是否结就与 thread 无关了。



6、是否存活(isAlive),即简单的理解,为 run 方法是否运行结束了

在这里插入图片描述
如果光是创建一个 thread 变量,不调用 start 则在系统内核里不会有线程。

创建变量就相当于是把一个任务梳理好了,而调用 start 就相当于是开始做任务。

在真正调用 start 之前,调用 thread.isAlive ,就是false。
调用 start 之后,isAlive 就是 true。

例子:

package thread;public class ThreadDemo8 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello java");}}, "Runnable");thread.start();while (true) {try {Thread.sleep(1000);System.out.println(thread.isAlive());} catch (InterruptedException e) {e.printStackTrace();}}}
}


isAlive 是在判断当前系统里面的这个线程是不是真的存在了。

run 执行完了,内核里的PCB就释放了。
操作系统里的线程就没了。
但是 thread 这个对象还在,当引用不指向这个对象没被GC回收时,thread 就不存在了。

总结:

  • 如果 thread 的 run 还没跑,isAlive 就是 false。
  • 如果 thread 的 run 正在跑,isAlive 就是 true。
  • 如果 thread 的 run 跑完了,isAlive 就是 false。


isAlive为true的例子: run 里面的线程会在执行3秒以后销毁。

package thread;public class ThreadDemo8 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 3; i++)  try {System.out.println("hello java");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}, "Runnable");thread.start();while (true) {try {Thread.sleep(1000);System.out.println(thread.isAlive());} catch (InterruptedException e) {e.printStackTrace();}}}
}


因为抢占式执行,hello java 在前还是 true 在前是不确定的。
要看调度器结果,这是不可预期的。


7、线程的中断问题(interrupted()),下面我们进一步说明。

3.启动一个线程 - start()

线程对象被创建出来并不意味着线程就开始运行了。

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

就像是前面说的,创建对象相当于是梳理任务,而调用 start 则是开始执行任务。

4.终止一个线程

终止意思不是让线程立即就停止,而是通知线程应该要停止了。
但是是否真的停止,取决于线程这里具体的代码的写法。

4.1 使用标志位来控制线程是否要停止

package thread;public class ThreadDemo9 {public static boolean flag = true;public static void main(String[] args) throws InterruptedException{Thread thread = new Thread(() -> {while (flag) {System.out.println("hello");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();Thread.sleep(3000);//3秒后结束线程//主线程这里可以随时通过flag变量的取值,来操作 thread 线程是否结束。flag = false;}
}

这段代码执行后3秒钟会结束线程。


自定义变量这种方式,不能及时响应。
尤其是在 sleep 休眠时间比较久的时候。

thread 线程之所以会结束,完全取决于 thread 线程内部的代码的 flag。
通过 flag 来控制循环。

这里只是告知线程要结束了,但是什么时候结束,都是取决于线程内部的代码是如何实现的

4.2 使用 Thread 自带的标志位来进行判定

package thread;public class ThreadDemo10 {public static void main(String[] args) throws InterruptedException{Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello juejin");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();Thread.sleep(3000);thread.interrupt();}
}

currentThread() 是Thread 类的静态方法
通过这个方法可以获取到当前线程。
哪个线程调用的这个方法,就是得到哪个线程的对象引用。

isInterrupted() 是在 thread.run中被调用的。
此处获取的线程就是 thread 线程。
为 true 表示被终止,为 false 表示未被终止。
这个方法的背后,就相当于是判断定一个 boolean 变量。

interrupt() 是用来终止 thread 线程的。
相当于是在设置这个 boolean 变量。

如果线程在 sleep 中休眠,此时调用 interrupt会把线程唤醒。从 sleep 提前返回了。
interrupt 会触发 sleep 内部的异常,导致 sleep 提前返回。


可以看到在3秒钟执行结束后,抛了一个异常后又继续执行了。

这是因为 interrupt会做两件事:

  • 把线程内部的标志位(boolean)给设置成true。
  • 如果线程在进行 sleep ,就会触发异常把 sleep 唤醒。
    但是 sleep 在唤醒的时候,还会做一件,就是把刚才设置的这个标志位再设置为 false 。(情况标志位)
    这就导致了当 sleep 的异常被 catch 完之后,循环还要继续执行。


当然也可以在唤醒 sleep 之后就停止,在刚才的代码中加入一个 break 即可。


可以看到抛丸异常就停止了。

怎样终止线程、什么时候终止,主要还是看代码是怎样实现的。

5.等待一个线程 - join()

线程是一个随机调度的过程。
等待线程做的事情,就是在控制两个线程的结束顺序。

package thread;public class ThreadDemo11 {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});thread.start();System.out.println("join之前");//此处的join就是让当前的main线程来等待thread线程指向结束(等待thread的run执行完)try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("join 之后");}
}

本身执行完 start 之后,thread 线程和 main 线程就并发执行分头行动。
main 继续往下执行,thread 也会继续往下执行。

join() 使线程发生阻塞,会一直阻塞到 thread 线程结束时,
main 线程才会从join中恢复过来,才能继续执行下去,因此 thread 线程肯定是比 main 先结束的。



main 线程等待 thread 执行结束后才会执行。

如果执行 join 时,thread 已经结束了,join 不会阻塞,就会立即返回。



join 还有另一种用法:

public void join(long millis)

此方法的含义是,指定等待的时间。
这种方式的操作比较常见。

上面的无参数的 join() 则会一直等待。

6.获取当前线程引用

public static Thread currentThread(); //返回当前线程对象的引用

在哪个线程调用,就能获取到哪个线程的实例。

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

7.休眠当前线程

//休眠当前线程 millis毫秒
public static void sleep(long millis) throws InterruptedException 
//可以更高精度的休眠
public static void sleep(long millis, int nanos) throws InterruptedException 

让线程休眠,本质上就是让这个线程不参与调度了。(不去CPU上执行了)


在操作系统里面有一个就绪队列和一个阻塞队列


操作系统每次需要调度一个线程去执行,就从就队列中选一个就好了。

PCB 是使用链表来组织的。(并不具体)
实际的情况并不是一个简单的链表,其实这是个一系列以链表为核心的数据结构。

线程 A 调用 sleep ,A 就会进入休眠状态。
把 A 从上述链表中拿出来,放到另一个链表中。


另一个链表里的PCB都是阻塞状态,暂时不参与 CPU 调度执行,是阻塞队列。

一旦线程进入阻塞状态,对应 PCB 就进入阻塞队列了,此时就暂时无法参与调度了。

比如调用 sleep(1000) ,对应的线程 PCB 就要在阻塞队列中待1000ms这么长的时间。


当这个 PCB 回到就绪队列后并不会被立即执行。
因为虽然是 sleep(1000),但是实际上考虑到调度的开销,
对应的线程是无法在唤醒之前之后立即执行的,实际上的时间间隔大概率要大于 1000ms。

相关内容

热门资讯

修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
【前端】‘??‘与‘||‘有什... 0 问题 经常写const data = res.data.a ?? ''或者const d...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
牛客计算器的改良(Python... 文章目录1.题目描述2.输入描述:3.输出描述:4.示例15.分析6.代码7.结语 链接࿱...
正大杯|市调大赛|2023备赛... 关键信息 同时随着精细化养宠趋势的深入,宠物消费类目日渐丰富。 本报告通过 Niuco...
QGIS下载在线地图(Goog... 前言 国内外有很多在线地图下载软件,但功能单一,基本上只能下载数据&#x...