JavaEE初阶——多线程(2)线程的使用
目录
一、 Thread类及常见方法
1.1 Thread的常见构造方法
1.2 Thread的几个常见属性
1.3 后台线程
1.4演示线程是否存活
1.5 演示线程中断
1.5.1通过自定义标志位中断
1.5.2 通过interrupt()方法中断
1.6 等待一个线程
1.7 获取当前线程
1.8 休眠当前线程
二、线程的状态
2.1 线程的所有状态
2.2 观察线程的不同状态
2.3 打印状态
我们之前已经成功创建了一个线程,使用到了Java中Thread类,详情可以看这一篇内容
JavaEE初阶——多线程(1)初识线程与创建线程-CSDN博客https://blog.csdn.net/Yoko_999/article/details/153076456?spm=1011.2124.3001.6209这一节我们具体来了解Thread类常用方法以及线程的状态
一、 Thread类及常见方法
Thread类是JVM⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关
联。
⽤我们之前在单线程学过的知识来看,每个执⾏流,会需要有⼀个对象来描述;⽽我们现在学习的线程执行流,就是由Thread对象来进行描述,一个线程就会有一个Thread对象,JVM会将这些Thread对象组织起来,⽤于线程调度,线程管理。
1.1 Thread的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
关于给线程的名字,首先我们要区分一下线程名字和Thread类的名字不是同一个概念,线程名字是指在操作系统上的线程名,我们可以使用我们之前提到的jconsole来观察线程名
public class Demo_01 {public static void main(String[] args) {Thread t1=new Thread(()->{while(true){}});t1.start();}
}
我们让线程循环不退出,然后在jconsole中可以看到Thread-0,这个就是我们的线程名,而不是代码中的t1
public class Demo_01 {public static void main(String[] args) {Thread t1=new Thread(()->{while(true){}},"线程1");t1.start();}
}
我们设置该线程的名字为“线程1”,再去jconsole观察,发现确实是这个名字
1.2 Thread的几个常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- 这里的ID是JVM默认为Thread对象生成的一个编号,注意这个编号是Java层面的,不是操作系统层面的,要和PCB区分开
- 状态表⽰线程当前所处的⼀个情况,也是Java层面定义的状态,和PCB区分开
- 优先级⾼的线程理论上来说更容易被调度到
- 线程分为前台线程和后台线程,通过这个标识位来区分当前线程为前台还是后台
- 是否存活,表示的是系统中的PCB是否销毁
- 是否被中断是设置一个标志位让线程在执行是判断是否要退出
我们通过一段代码来看一下线程的各个字段的具体内容
public class Demo_03 {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(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}//退出循环System.out.println(Thread.currentThread().getName()+"我即将运行结束");});//主线程System.out.println(thread.getName()+": ID "+thread.getId());System.out.println(thread.getName()+": 名称 "+thread.getName());System.out.println(thread.getName()+": 状态 "+thread.getState());System.out.println(thread.getName()+": 优先级 "+thread.getPriority());System.out.println(thread.getName()+": 是否后台线程 "+thread.isDaemon());System.out.println(thread.getName()+": 是否存活 "+thread.isAlive());System.out.println(thread.getName()+": 是否被中断 "+thread.isInterrupted());//启动线程thread.start();while(thread.isAlive()){}System.out.println(thread.getName()+": 状态 "+thread.getState());}
}
代码中我们先运行主线程的内容,打印线程未开始之前的字段状态,然后线程开始运行,通过一个while循环等thread退出之后再打印当前状态
1.3 后台线程
我们来通过代码演示一下后台线程
public class Demo_04 {public static void main(String[] args) {Thread thread=new Thread(()->{while(true){System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//启动线程thread.start();System.out.println("线程是否存活:"+thread.isAlive());System.out.println("main 方法执行完成");}
}
当前线程为前台线程,我们运行看下结果
可以看到主线程中main方法只想完成,结束之后thread线程还在运行
public class Demo_04 {public static void main(String[] args) {Thread thread=new Thread(()->{while(true){System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//设置该进程为后台进程thread.setDaemon(true);//启动线程thread.start();System.out.println("线程是否存活:"+thread.isAlive());System.out.println("main 方法执行完成");}
}
我们在启动之前通过thread.setDaemon(true)把进程设置为后台进程,我们再运行来看结果
可以看到主线程结束之后,整个程序就结束了,所以我们可以理解为JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
1.4演示线程是否存活
public class Demo_05 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello thread.. ");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程执行完成。");});System.out.println("启动之前查看线程是否存活:" + thread.isAlive());// 启动线程thread.start();System.out.println("启动之后查看线程是否存活:" + thread.isAlive());// 等待线程执行完成thread.join();// 保证PCB已销毁Thread.sleep(1000);System.out.println("线程结束之后查看线程是否存活:" + thread.isAlive());}
}
第一个false是PCB还未创建,true代表PCB存在,第二个false代表PCB已经销毁
1.5 演示线程中断
中断就是当任务进行到一半需要停止,通过一个信号使线程退出
例如我在学习,突然一个电话来了,我需要停止学习去接电话
1.5.1通过自定义标志位中断
public class Demo_206 {// 定义一个标志位static boolean isQuit = false;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {while (!isQuit) {System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}// 线程退出System.out.println("线程退出...");});// 启动线程thread.start();// 休眠5秒Thread.sleep(5000);// 修改标志位,相当于电话进来的中断信号isQuit = true;}
}
我们能看到休眠的五秒内线程打印了5次,然后我们修改标志位之后线程中断退出
1.5.2 通过interrupt()方法中断
我们先来学习一下中断有关的方法
方法 | 说明 |
public void interrupt() | 中断对象关联的线程 |
public static boolean interrupted() | 判断当前线程中断标志位是否设置 |
public boolean isInterrupted() | 判断对象灌篮的线程标志位是否设置 |
我们解析一下代码,首先我们调用isInterrupted作为while循环的标志,每打印一句话之后就休眠1秒,然后我们在线程启动之前和运行中和中断之后查看线程是否存活
public class Demo_207 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {// 通过线程对象内部维护的中断标识,判断当前线程是否需要中断while (!Thread.currentThread().isInterrupted()) {// 线程中具体的任务是打印一句话System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程已退出");});System.out.println("线程是否存活:" + thread.isAlive());// 启动线程thread.start();// 休眠一会Thread.sleep(1000);System.out.println("线程是否存活:" + thread.isAlive());// 中断线程,发出中断信号thread.interrupt();// 等待线程销毁Thread.sleep(100);// 查看是否存活System.out.println("线程是否存活:" + thread.isAlive());}
}
我们来看运行结果发现,在线程运行之后报错sleep interrupted,这是因为我们这个线程每次打印之后都要休眠1秒,线程大部分时间处于休眠。在调用interrupt方法时,线程也处于休眠状态,所以报错。而且调用完中断方法之后,线程没有像我们预想的那样结束运行,而是还存活并且执行任务。
也就是说明当线程休眠时调用interrupt方法,会报错而且线程也不会被中断
那么我们就需要处理一下这种情况:当捕获到中断报错时,我们手动break退出
public class Demo_07 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {// 通过线程对象内部维护的中断标识,判断当前线程是否需要中断while (!Thread.currentThread().isInterrupted()) {// 线程中具体的任务是打印一句话System.out.println("hello thread...");// 线程大部分时间在sleeptry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println("休眠被中断");// 处理中断逻辑break;}}System.out.println("线程已退出");});System.out.println("线程是否存活:" + thread.isAlive());// 启动线程thread.start();// 休眠一会Thread.sleep(1000);System.out.println("线程是否存活:" + thread.isAlive());// 中断线程,发出中断信号thread.interrupt();// 等待线程销毁Thread.sleep(100);// 查看是否存活System.out.println("线程是否存活:" + thread.isAlive());}
}
这样线程在中断之后也退出了,避免调用interrupt方法之后线程仍在执行
我们来看一下正常状态下调用中断方法的运行结果
public class Demo_08 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {// 通过线程对象内部维护的中断标识,判断当前线程是否需要中断while (!Thread.currentThread().isInterrupted()) {// 线程中具体的任务是打印一句话System.out.println("hello thread...");}System.out.println("线程已退出");});System.out.println("线程是否存活:" + thread.isAlive());// 启动线程thread.start();// 休眠一会Thread.sleep(1);System.out.println("线程是否存活:" + thread.isAlive());// 中断线程,发出中断信号thread.interrupt();// 等待线程销毁Thread.sleep(1);// 查看是否存活System.out.println("线程是否存活:" + thread.isAlive());}
}
这次我们在代码中不进行休眠了,让线程任务一直处于执行状态,此时调用中断方法就可以正常中断
我们看到调用中断之后线程自动中断退出
调用interrupt()方法时:
1.如果线程在运行状态,直接中断线程,不会报异常
2.如果线程在等待状态,就会报一个中断异常,我们要在异常处理代码块中手动break进行中断逻辑实现
1.6 等待一个线程
在某些场景下,任务不可以并行运行,而是需要等待一个线程运行完成后才可以决定当前线程的行为。比如:只有等包子店的包子出炉了我才能买;只有等别人给我转账之后,我才可以存这笔钱。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,但是最多等millis毫秒 |
public void join(long millis,int nanos) | 同理等待线程结束,但是时间精度更高 |
public class Demo_09 {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 5; 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("王五⼯作结束了");}
}
我们能看到,线程1开启后我们是等到他结束之后再执行线程2
如果把join去掉之后我们看一下结果
我们能看到,如果去掉join之后,两个线程就并行了,没有等待这一行为
1.7 获取当前线程
方法 | 说明 |
public static Thread currentThread(); | 返回当前线程对象的引用 |
1.8 休眠当前线程
方法 | 说明 |
public static void sleep(long millis),throws InterruptedException | 返回当前线程对象的引用 |
public static void sleep(long millis,int nanos) throws InterruptedException | 更高精度控制休眠时间 |
二、线程的状态
2.1 线程的所有状态
状态 | 说明 |
NEW | 安表示创建好了一个Java线程对象,安排好任务,但是还未启动,没有调用start()方法之前不会创建PCB |
RUNNABLE | 可工作的,是运行和就绪的状态,在执行任务是最常见的一种状态,在系统中有对应的PCB |
BLOCKED | 等待锁的状态,阻塞中的一种 |
WAITING | 等待状态,但是没有等待时间,一直死等,直到被唤醒 |
TIMED_WAITING | 等待状态,但是指定了等待时间的阻塞状态,超过等待时间之后就不再等候 |
TERMINATE | 完成状态,PCB已经销毁,但是Java线程对象还存在 |
2.2 观察线程的不同状态
public class Demo_10 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for (int i = 0; i < 10_0000_0000l; i++) {}});// 启动之前看一下线程的状态System.out.println("启动之前 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());// 启动线程thread.start();// 启动之后System.out.println("启动之后 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());// 等待线程执行完成thread.join();// 线程结束之后查看状态System.out.println("结束之后 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());}
}
能看到线程调用start()方法之前处于NEW状态,安排好工作,处于即将开始工作的状态,PCB还没创建;调用start()方法之后开始运行,PCB已经创建;调用join()方法,等待完成之后处于TERMINATED结束状态,PCB已经销毁不存活。
public class Demo_301 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello thread...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});// 启动之前看一下线程的状态System.out.println("启动之前 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());// 启动线程thread.start();// 查看状态之前等待一会TimeUnit.MILLISECONDS.sleep(500);// 启动之后System.out.println("启动之后 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());// 等待线程执行完成thread.join();// 线程结束之后查看状态System.out.println("结束之后 :" + thread.getState());System.out.println("是否存活 :" + thread.isAlive());}
}
我们把线程任务加上一个等待的操作,这样线程大部分时间处于等待的情况,我们再来查看线程状态
这样线程开始运行之后,我们查看状态,看到的就不是原来的RUNNABLE状态了,而是TIMED_WAITING状态了
2.3 打印状态
Java中的线程状态是存储在一个枚举中,我们可以打印查看一下
public class Demo_11 {public static void main(String[] args) {// 线程状态 定义在一个枚举中for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}