目录
1.1 getId()、getName()、getState()、getPririty()
1.6 Interrupt()、interrupted()、isInterrupted()
一、Thread概述
Thread类是JVM用于管理线程的类,每一个线程都与一个唯一的Thread对象相关联,即每个执行流都由一个Thread对象进行描述,这些对象被JVM组织,用于线程调度和管理。 |
二、构造方法
构造方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable接口实现类对象,创建线程对象 |
Thread(String name) | 创建线程对象,并为线程对象命名 |
Thread(Runnable target, String name) | 使用Runnable接口实现类对象,创建线程对象,并为线程对象命名 |
Thread(TreadGroup group, Runnable target) | 指定线程组,使用Runnable接口实现类对象,创建线程对象 |
三、常用方法
常用方法 | 说明 |
getId() | 获取线程ID |
getName() | 获取线程名 |
getState() | 获取线程状态 |
getPririty() | 获取线程优先级 |
start() | 启动线程 |
isDaemon() | 判断线程是否为后台线程(守护线程) |
setDaemon() | 设定线程是否为后台线程(守护线程) |
isAlive() | 判断线程是否“存活” |
currentThread() | 获取当前线程的引用 |
Interrupt() | 终止一个线程 |
interrupted() | 判断当前线程标志位状态 |
isInterrupted() | 判断对象线程标志位状态 |
sleep() | 休眠线程 |
join() | 阻塞线程 |
1.1 getId()、getName()、getState()、getPririty()
getId() |
获取线程ID,ID是线程的唯一标识,由JVM自动分配并确保唯一性。 |
getName() |
获取线程名,线程名可以自动生成,也可以自定义。线程名可以重复。 |
getState() |
获取线程状态,线程的状态有就绪、阻塞等。Java中现成的状态使用枚举保存,可以通过遍历枚举获得所有状态的描述。 |
阅读指针 -> 《Java中线程有多少种状态(State)?状态之间的关系有什么关系?》
getPririty() |
获取线程优先级,优先级高的线程理论上更容易被调度使用,但是在Java中优先级的效果并不明显。 |
操作演示:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Thread_Demo6 {
public static void main(String[] args){
Thread thread = new MyThread();
System.out.println("ID:"+thread.getId());
System.out.println("线程名:"+thread.getName());
System.out.println("状态:"+thread.getState());
System.out.println("优先级:"+thread.getPriority());
}
}
打印结果:
ID:20
线程名:Thread-0
状态:NEW
优先级:5
1.2 start()
start() 启动线程 |
1)通过重写Thread中的run方法可以创建一个线程对象,再通过调用start()方法,启动这个线程。此时,操作系统中的线程才真正被创建出来。 |
2)Thread调用start()创建出的线程,底层仍然是调用系统的API来进行创建线程的操作。 |
3)Thread类使用start方法启动线程,对于同一个Thread对象,start方法只能调用一次,需要启动多少个线程,就需要创建多少个Thread对象。 |
4)start()和run()的区别在于,run方法是提供了线程需要运行的内容,而start方法才是真正让线程运行起来。 |
同一个Thread对象不能多次调用start方法演示:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<2;i++){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo9 {
public static void main(String[] args) {
Thread thread = new MyThread();
//第一次启动线程thread;
thread.start();
//第二次启动线程thread;
thread.start();
}
}
打印结果:
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at Learn_Thread.Demo9.Thread_Demo9.main(Thread_Demo9.java:32)
这是MyThread
这是MyThread
MyThread-run()运行结束
可以看到两次调用start方法,只有一次成功执行,另一次报错IllegalThreadStateException
1.3 isDaemon()、setDaemon()
isDaemon() 判断线程是否为后台线程 |
setDaemon() 设定线程是否为后台线程 |
1)daemon的意思是守护,因此也将后台线程称为守护线程。与后台线程相呼应,还有前台线程。 |
2)代码创建的线程,默认为前台线程。当setDaemon()方法的参数为false时,线程将被设置为前台线程,当参数为true时,线程将被设置为后台线程。 |
3)前台线程的运行时,将阻止进程结束;后台线程运行时,不会阻止进程结束。 |
4)为什么要将后台线程称为守护线程?就是说进程需要我就在,进程不要我就走,在背后默默守护进程的线程这个意思? |
后台进程的执行演示:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Thread_Demo7 {
public static void main(String[] args) {
Thread thread = new MyThread();
//设置为守护线程(后台线程);
thread.setDaemon(true);
//开始thread;
thread.start();
//main线程等待两秒后结束,此时守护线程还有代码没打印,但也随之结束了;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程结束");
}
}
打印结果:
这是MyThread
这是MyThread
main线程结束
启动main线程和thread线程,在main线程sleep两秒结束线程后,可以看到thread线程的run()方法打印结果确实不是预期中的五次“这是MyThread”。这意味着thread线程也随着main线程的结束而结束了。
1.4 isAlive()
isAlive() 判断线程是否“存活” |
1)Java中的线程类Thread对象实例,虽然表示一个线程,但这个实例的生命周期与系统内核中的线程的生命周期是不同的。 |
2)Thread对象创建了就存在,但此时如果调用isAlive()得到的结果将会是false,因为内核中的线程此时还不存在。 |
3)只有当调用start()启动线程之后,内核中的线程启动,调用isAlive()得到的结果才会是true。 |
4)当线程运行结束,系统内核中的线程也随之结束,此时虽然Thread对象还存在,但是调用isAlive()得到的结果也将是false。 |
5)因此,可以简单将这个方法认为是用于判断系统内核中的线程(PCB)是否存在。 从代码层面来讲,可以认为是用于判断run()方法是否执行完毕。 |
演示判断线程是否“存活”:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<2;i++){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo8 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
//线程已有实例,判断isAlive();
System.out.println("start前,thread是否存活:"+thread.isAlive());
//启动线程,判断isAlive();
thread.start();
System.out.println("start中,thread是否存活:"+thread.isAlive());
//等待run()执行结束,此时实例依旧存在,判断isAlive();
Thread.sleep(5000);
System.out.println("start结束,thread是否存活:"+thread.isAlive());
}
}
打印结果:
start前,thread是否存活:false
start中,thread是否存活:true
这是MyThread
这是MyThread
MyThread-run()运行结束
start结束,thread是否存活:false
1.5 currentThread()
currentThread() 获取当前线程的引用 |
1)在currentThread()方法返回的打印信息中,有三个值,分别代表[线程名,线程优先级,所在线程组]。 |
获取线程引用操作演示:
public class Thread_Demo11 {
public static void main(String[] args) {
//打印main线程信息;
System.out.println(Thread.currentThread());
}
}
打印结果:
Thread[main,5,main]
中括号中的三个值分别代表:线程名,线程优先级,所在线程组。
1.6 Interrupt()、interrupted()、isInterrupted()
如果想要中断(终止)一个线程,可以有多种方法,以下介绍两种:
方法一: | 通过共享的标志进行线程间沟通。 |
方法二: | 调用Interrupt()方法。 |
1.6.1 方法一:添加共享的标志位
class MyThread extends Thread{
//设置共享的标志位;
public volatile boolean isQuit = false;
@Override
public void run() {
//根据标志位的变化,决定后续执行;
while (!isQuit){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
//启动线程;
thread.start();
//延时两秒后更改标志位;
Thread.sleep(2000);
((MyThread) thread).isQuit = true;
}
}
打印结果:
这是MyThread
这是MyThread
这是MyThread
MyThread-run()运行结束
1.6.2 方法二:使用内置的标志位
方法 | 说明 |
public void interrupt() | 如果线程处于阻塞状态,则抛出异常; 如果线程不处于阻塞状态,则终止线程。 |
public static boolean interrupted() | 判断当前线程标志位状态。 |
public boolean isInterrupted() | 判断对象线程标志位状态。 |
1)上文由程序员手动设置了一个共享的标志位,用于控制线程的执行。Java中也提供了相应的封装好的方法,内置了标志位。使用以上三个方法,一样可以达到控制线程的执行效果。 |
通过Interrupt()终止线程,然后对线程标志位状态进行判断:
class MyThread extends Thread{
@Override
public void run() {
//使用isInterrupted()判断标志位状态;
while (!Thread.currentThread().isInterrupted()){
System.out.println("这是MyThread");
//打印当前线程标志位的状态;
System.out.println(Thread.interrupted());
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
//延时两秒后更改标志位;
Thread.sleep(2000);
thread.interrupt();
}
}
打印结果:
......
这是MyThread
false
这是MyThread
false
这是MyThread
false
MyThread-run()运行结束
休眠两秒后,thread通过调用interrupt()方法修改了标志位,线程终止执行。
当更改标志位,但线程处于阻塞状态时:
在更改标志位时,如果线程因为调用 wait/join/sleep 等方法而阻塞,则此时会抛出异常InterruptedException,并重置终止标志位。此时程序的后续执行通过catch子句中的异常处理方案来决定。 |
如果在更改标志位时,线程非为阻塞状态,则标志位不会重置,可以通过interrupted()或isInterrupted()进行判断。 |
class MyThread extends Thread{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo14 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
//等待两秒后唤醒;
Thread.sleep(2000);
thread.interrupt();
}
}
打印结果:
这是MyThread
这是MyThread
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Learn_Thread.Demo14.MyThread.run(Thread_Demo14.java:16)
这是MyThread
这是MyThread
这是MyThread
可以看到,在抛出异常后,仍然继续打印,这意味着原先由interrupt()方法修改的标志位,在sleep唤醒时,又被重置为false了。
在异常处理中,加入更多的处理方法:
class MyThread extends Thread{
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//在异常处理中加入break,跳出循环;
break;
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo14 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
//等待两秒后唤醒;
Thread.sleep(2000);
thread.interrupt();
}
}
打印结果:
这是MyThread
这是MyThread
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Learn_Thread.Demo14.MyThread.run(Thread_Demo14.java:16)
MyThread-run()运行结束
线程通过interrupt()方法修改了标志位,但由于线程此时大概率处于sleep(即,阻塞状态),因此,抛出异常,并将标志位重置。
这里线程会终止,是由异常处理中的break跳出循环得到的结果。
1.6.3 Java中终止线程不是强制性的
操作系统中的API: | 提供了强制终止线程的操作,无论线程执行到何种程度,都强行结束线程。 |
Java中的的API: | 终止线程需要对应线程互相配合,而不是直接“剪断”。 |
优劣: | 强制结束线程的方式更“随心所欲,为所欲为”,但如果线程执行过程中被强行终止,可能导致出现一些临时性质的“错误”数据。而相互配合的线程终止,虽然使终止线程时需要考虑的事情变多了,但也使得线程的终止更“安全”,系统运行更稳定了。 |
1.7 sleep()
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 以毫秒级别的精度,指定休眠时间 |
public static void sleep(long millis, int nanos) throws InterruptedException | 以纳秒级别的精度,指定休眠时间 |
1)sleep()方法只能保证实际休眠时间大于等于参数设置的休眠时间。 |
2)sleep被提前唤醒(如被上文的interrup唤醒)时,会抛出异常,并将Thread对象的标志位重置为false。 |
sleep被提前唤醒时,为什么要重置标志位?
sleep重置标志位,可以给程序员更多的“可操作空间”。 通过抛出异常,处理异常,程序的后续执行可以且不仅可以让线程立即结束,增加了代码的灵活性。 |
1.8 join()
join() 等待线程结束 |
join(long millis) 等待线程结束,指定最长等待时间 |
join(long millis, int nanos) 等待线程结束,以纳秒级别的精度指定最长等待时间 |
1)由于随即调度,抢占式执行,多线程的执行顺序是不确定的。但是通过应用程序中的API,可以影响到线程的结束顺序。 |
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("这是MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyThread-run()运行结束");
}
}
public class Thread_Demo13 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
//让主线程阻塞,保证MyThread先运行完;
thread.join();
System.out.println("在MyThread结束后打印");
}
}
打印结果:
这是MyThread
这是MyThread
这是MyThread
这是MyThread
这是MyThread
MyThread-run()运行结束
在MyThread结束后打印
可以看到,main线程中的“在MyThread结束后打印”确实是在thread线程结束后才打印的。
阅读指针 -> 《 多线程重点!! -- 线程安全 》