目录

​​1.Thread类常用的构造方法​​

​​2.Thread类的几个常见属性​​

​​2.1 什么是守护线程?isDaemon​​

​​2.2线程是否存活 isAliye()​​

​​3.终止线程的方法​​

​​3.1使用共享标志位通知中断线程​​

​​3.2使用Thread自带的标志位通知​​

​​4.等待线程 join​​

​​5.获取当前线程的引用​​

​​6.休眠当前线程​​


Thread类的常用方法_jvm

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联

1.Thread类常用的构造方法

方法

说明

Thread()

创建线程对象

Thread(Runnable target)

使用Runnable对象创建线程对象

Thread(String name)

创建线程对象并命名

Thread(Runnable target,String name)

使用Runnable对象创建线程对象并命名

多了个name参数是为了给线程一个名称方便调试线程

我们使用最后一个方法创建对象并且命名,然后再java工具中找到这个name的线程

public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("hello world!!");
}
}
},"myThread");
t.start();
}
}

Thread类的常用方法_开发语言_02

 

Thread类的常用方法_jvm_03

 

Thread类的常用方法_开发语言_04

 这里就可以看到我们刚刚创建并且命名的线程了,t是代码里面的变量名,myThread是线程名

可以看到这里这里没有main线程了,是因为主线程执行完了start之后就紧接着结束了main方法,对主线程来说,main方法没了,自己也就结束了

同样的,当run方法执行完了,myThread线程也就自动结束了

2.Thread类的几个常见属性

属性

获取方法

说明

ID

getId()

获取到线程的身份标识

名称

getName()

获取到构造方法中的名字

状态

getState()

获取到线程的状态(Java中的线程的状态比操作系统的原生的状态更丰富一些)

优先级

getPriority()

获取或者设置优先级

是否后台线程

isDaemon()

是否是守护线程

是否存活

isAliye()

判定线程是否存活

是否被中断

isInterrupted()

是否被中断

2.1 什么是守护线程?isDaemon

Thread类的常用方法_java_05

java中的线程分为前台和后台线程,代码里手动创建的线程,Main线程都是前台的线程,其它的JVM自带的线程都是后台的线程,也就是守护线程.

也可以通过setDaemon来手动设置成后台线程

前台线程和后台线程的特点:

前台线程会阻止进程的结束,前台线程的工作没有做完,进程是不能结束的,后台线程不会阻止进程结束,后台工作没做完,进程是可以结束的

刚刚在上面创建的myThread线程默认是前台线程,我们使用 setDaemon来手动设置成后台线程

Thread类的常用方法_jvm_06

t线程还没执行完,但是进程直接结束了

 当t被设置成守护线程,那么进程的结束与否就与t没有关系了,此时前台线程只剩下主线程了,主线程什么时候执行完,进程什么时候结束

2.2线程是否存活 isAliye()

Thread类的常用方法_开发语言_07

 回顾一下线程是如何被创建的

Thread类的常用方法_System_08

 因此,在调用start()之前调用isAliye()结果应该是false,调用start()之后调用isAliye()结果应该是true.

isAliye()是在判断当前系统里的线程是否是真的创建了,如果内核中的线程把run()执行完了,PCB释放了,但是t这个对象不一定被释放,此时isAliye()被调用后还是false

也可以看出t这个对象的生命周期,是比内核中的PCB生命周期长的

下面看代码演示:

public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world!!");
}
},"myThread");
t.start();
while(true){
try {
Thread.sleep(1000);
System.out.println(t.isAlive());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}

run()方法执行完了打印helloworld之后线程就结束了,即PCB没有了, 但是t对象还在,此时再去调isAlive(),结果就是false.

当引用不指向对象,t才会被GC回收

Thread类的常用方法_System_09

如果让run()方法执行慢一点,就能看到true了

将run()方法改为

public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("hello world!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

运行后run()3s后才被销毁,期间isAlive()返回true,3s后线程结束,isAlive()返回false

Thread类的常用方法_jvm_10

 线程之间是并发执行并且是抢占式调度的,因此先执行哪个线程是不确定的,这里根据结果我们大概可以看到,第一轮,主线程先sleep,先执行t线程,然后打印了helloworld!!,紧接着也进入了sleep,接下来就是看谁先唤醒,然后继续执行,,从结果看到执行的是hello world!!因此还是t线程先唤醒,继续执行,第一轮执行结束,主线程打印true.进入sleep,第二轮开始,t线程打印helloworld!!,然后主线程打印true,进入sleep,然后时间来到了3s,t线程结束,主线程一直打印false

这两轮操作除了第一个helloworld是确定的,后面的hello在前还是true在前都是无法确定的,完全看调度器是如何调度的结果,这就是抢占式执行

看一个清楚点的例子:

public class ThreadDemo7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hello world!!");
});
t.start();
System.out.println("hello main!!");
}
}

这两个线成所执行的顺序完全就是随机的,也就是先打印出哪个,完全是不确定的 ,这和我们以前写代码,是固定的顺序执行的是不同的,理解多线程代码需要考虑到无数的顺序

总结一下

当run()没跑的时候,isAlive()返回false 

当run()正在跑的时候,isAlive()返回true

当run()跑结束的时候,isAlive()返回false 

3.终止线程的方法

中断一个线程不是让线程立即就停止工作,只是告诉它应该停止了,是否真的停止,要看线程内的代码逻辑

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

3.1使用共享标志位通知中断线程

在主线程创建一个共享标志位flag,通过它的改变来通知线程是否中断

public class ThreadDemo8 {
private static boolean flag = true;
public static void main(String[] args) {
Thread t = new Thread(()->{
while (flag){
System.out.println("hrllo world!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
flag = false;
}
}

 运行结果

Thread类的常用方法_System_11

主线程sleep3s,因此t线程运行3s之后,共享标志位发生改变,t线程就结束了

注意:这个代码之所以能修改了标志位,就起到中断t的作用,完全取决于线程t的代码是什么逻辑,如果代码内部不用flag控制循环,那么标志位的改变也不起任何作用,因此,这个标志位的改变咱们就是说只是通知它要中断了,是否中断还是看代码内部来决定的

自定义的共享标志位这种方式,也是不能及时响应的,像循环里有sleep的代码,无法在更改标志位后立即响应,这时线程t已经执行了很多代码了,才会收到通知,下来我们看一下使用Thread自带的标志位通知

3.2使用Thread自带的标志位通知

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

Thread.currentThread()

是Thread类的静态方法,通过这个方法可以获取到当前线程的对象的引用,谁调用它就会获取谁的对象的引用,类似于this

isInterrupted()

为true表示被终止,为false表示继续执行

!Thread.currentThread().isInterrupted()

如果在t.run()中被调用,获取的就是t线程

加了!是逻辑取反,当isInterrupted()是true时,是要中断线程的,while(flag)中flag取false才能中断,因此要加!来取反

t.interrupt()

表示终止线程,哪个对象调用就终止呢个线程,这里main线程调用t.interrupt()表示的是main线程通知t线程你该终止了!!

isInterrupted()相当于设置一个布尔变量,t.interrupt()方法是在设置这个变量.3.1中的方法是直接操作一个布尔变量,3.2的方法是把布尔变量操作封装到Thread方法中了

封装到Thread类之后,如果线程在sleep中休眠,此时调用interrupt()来唤醒t线程,这时就不继续休眠了.interrupt()会直接触发sleep中的异常


InterruptedException


导致sleep提前返回了,我们看一下上面代码的运行结果

Thread类的常用方法_主线程_12

 可以发现:前三次是正常执行的,然后调用t.interrupt()触发异常后t线程中断了,中断之后t线程又继续运行了!!这是什么原因呢?

是因为t.interrupt()后首先把线程内部的标志位设置成true,若线程再进行sleep,会唤醒sleep,触发异常!!但是还没完,触发异常之后,又将标志位在设置回false,相当于清空了标志位,导致了sleep的异常被catch了之后,代码继续执行!!因此我们看见的结果是先运行然后中断,然后继续运行!还要注意调用t.interrupt()只是main线程通知t线程终止,t线程并不是一定会终止!!

 这种情况是线程t忽略了main的终止请求

 

Thread类的常用方法_System_13

加上break后,线程t就立即响应main的终止请求 

 

Thread类的常用方法_主线程_14

这种情况是稍后进行终止

Thread类的常用方法_主线程_15

 在catch处可以添加任意代码,因此为什么sleep的标志位会被清除就很明了了,在sleep被唤醒之后,线程到底是要终止还是继续运行,选择权就交给程序员了!!可以通过代码控制到底继续执行还是终止

4.等待线程 join

线程是一个随机调度的过程,等待线程就是控制两个线程的结束顺序,因为线程调度是随机的,我们无法控制线程的开始顺序,但是能通过方法控制线程的结束顺序

使用的是join()方法

public class ThreadDemo10 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("hello world!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("join前");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("join后");
}
}

此处的join()方法作用就是让main线程等待t线程执行结束

当start启动t线程时,主线程和t线程就开始运行了,主线程走到t.join()时,就发生阻塞了!!

一直阻塞到t线程执行结束,主线程才会从阻塞中恢复过来,才能继续往下执行!!因此t线程一定是比主线程先结束的,就达到了有序结束线程的目的!

Thread类的常用方法_java_16

从结果也可以看出来,两个线程开始运行后,主线程先打印了join之前,然后t线程执行,此时主线程阻塞了,t线程执行的时候并没有继续执行,也就是打印join之后,当t执行完了,才打印的join之后

还有一个问题,如果主线程执行join之前,t线程就结束了,会发生什么?

调整一下代码,让主线程sleep5s,其它地方不变,t线程3s就结束了,我们看效果 

Thread类的常用方法_主线程_17

 结果:t线程执行结束后,主线程是没有阻塞的,直接返回了,执行结束了 

Thread类的常用方法_开发语言_18

因此假设开始执行join时,t已经结束了,join就不会再阻塞了,而是会立即返回!!

 join方法有下面三种:

方法

说明

public void join()

一直等待,等到线程结束

public void join(long mills)

等待线程结束,最多等 millis 毫秒

public void join(long mills,int nanos)

等待线程结束,最多等 millis 毫秒,但可以更高精度

第二种比较常用,如果等到一定的时间没有结果,就不等了,一直死等会让程序无法继续执行了!

5.获取当前线程的引用

这个方法之前就已经了解过了

public static Thread currentThread();

功能:返回当前线程对象的引用

这是一个静态方法,可以直接通过类名调用,不需要实例化对象来调用

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

 结果是获取当前线程的对象的引用

Thread类的常用方法_java_19

该方法是通过类名调用的,也就是类方法,为什么不构造一个新的关键字表示"类属性类方法"?

当构造一个新的关键字,必然要有一个名字,这个名字肯定不能作为变量名,方法名,一旦引入新的关键字,就需要考虑改动所有和它冲突的方法名,变量名,那么带来的结果是很严重的,因此就直接把static关键字拿过来使用了,不管他本来什么意思,直接赋予了新的使命! 

6.休眠当前线程

本质上是让这个线程不参与调度,不去cpu上执行了

线程调度是不可控的,因此休眠时间会大于等于参数设置的时间,即使你参数设置的时间到了,也不一定会立即调度然后执行这个线程

之前谈到过就绪队列,链表中的PCB都是等待调度的就绪状态,如果A线程在就绪队列中,并且调用sleep,那么A就会立即进入一个阻塞队列,这个队列的PCB都是阻塞的,不参与调度.

当这个PCBsleep结束之后回到就绪队列,考虑到实际的调度开销,对应的线程回到就绪队列后是无法立即被调度的!!!

看一下休眠的方法

方法

说明

public static void sleep(long millis) throws InterruptedException

休眠当前线程 millis毫秒

public static void sleep(long millis, int nanos) throwsInterruptedException

高精度的休眠

public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
}

Thread类的常用方法_开发语言_20

 结果是main线程休眠了3s然后继续执行!