Thread常用的方法及线程状态原因解析

温馨提示:PC端观看,效果更佳!

本文两大部分:介绍一些Thread线程常用的方法,和解析线程的六大状态!

目录

一、线程常用的方法

介绍Thread类

1.开启线程

2.获取线程引用

3.休眠线程

4.中断线程

3.等待线程

二、线程状态

1.NEW

2.TERMINATED

3.RUNNABLE

4.WAITING

5.TIMED_WAITING

6.BLOCKED

7.状态总结(超级重要)


一、线程常用的方法

介绍Thread类

(1)这是一个线程类,用来实例化线程对象

(2)该类中,有很多方法。普通成员方法,可以使用对象的引用进行调用,像statr()方法等等;而静态方法,则需要通过类名去调用,如:Thread.sleep()等等。

(3)我们通过Thread来实例化对象,产生的对象,我们默认称为"线程对象",start()之后,线程才产生。因此,类中拥有的各种属性方法,我们后面就默认称为该线程的属性和方法。

(4)线程,有哪些状态、属性,方法,本质上都是Thread中的;因此我们实例化出的线程,也就具有了。后续所说的线程,都是指 线程对象

1.开启线程

(1)方法:start()

(2)作用:当线程对象调用该方法之后,真正的线程才真正的被创建出来并且执行。

(3)实例

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("张三线程") {
            @Override
            public void run() {
                System.out.println("111");
            }
        };
        t.start();
    }

在:该操作,只是创造出了线程对象

java 复制代码
Thread t = new Thread("张三线程")

只有:start()之后,该线程才是真正创建并运行了起来(真正在系统中创造了PCB)

java 复制代码
 t.start();

(4)start()注意事项

每一个线程对象,只可以start()一次。第二次start()操作就会出现异常

异常:非法的线程状态

该线程状态:

原因:线程start()之后,被调度到cpu上执行,当线程中的任务执行完之后,就会被销毁了;此时线程(PCB)已经不存在了,但是Thread对象(t引用指向的对象)还存在。

2.获取线程引用

(1)作用:返回当前线程对象的引用(在哪个线程中调用,就返回哪个线程的)

(2)方法全貌:public static native Thread currentThread()

(3)返回自定义线程的引用

java 复制代码
public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println(Thread.currentThread().getState());
            System.out.println("开启线程成功!");
        }) {
        };
        t.start();
    }

获取t线程的引用,并且调用该线程的状态

(4)获取主线程的引用

java 复制代码
 public static void main(String[] args) {
        Thread t = Thread.currentThread();//此时t中存的是main线程的引用
        System.out.println("线程名字:"+t.getName());
    }
3.休眠线程

(1)方法:Thread.sleep()。很明显,这是一个静态方法,需要传参。

参数是整数,单位是ms。1000ms等于一秒

(2)作用:让线程休眠

(3)实例

java 复制代码
public static int m = 4;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                    m--;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(m+"秒后结束t线程");
                if(m == 1) break;
            }
            System.out.println("t线程结束");
        });
        t.start();
    }

作用:每循环一次,就会休眠一秒,当休眠三秒后,线程就会结束。

(4)使用的注意实现1

1)在重写的run方法中

原因:在方法重写的时候,要求方法前面是一样的,run方法的父类是没有抛出异常的,所以run方法也不能抛出异常。所以sleep异常处理选择时,就不能抛出异常,只能捕获异常。

2)在外面

(5)sleep的注意事项2

sleep控制的是线程的休眠时间,而不是两个代码之间的时间

在时间上,并不是1000ms.

得出一个小结论:sleep结束之后,线程不一定会马上被调度会cpu上执行。

4.中断线程

更加合理的说法是说,终止一个线程。按理来说,当线程开启的时候,就会一直执行,直到线程的任务结束。但是有时候,需要停止线程,就需要一些操作去执行了。而终止线程,只是起到提醒的作用,线程是否要终止,还要看线程本身。

目前有两种方式:

(1)通过共享的标记来进行沟通(2)调用interrupt()方法来通知

2.1.第一种实现

(1)标志正确写法

java 复制代码
    public static boolean isRunning = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (isRunning) {
                System.out.println("线程正在执行!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程结束了");
        });
        t.start();
        System.out.println("3s后结束线程");
        Thread.sleep(1000);
        isRunning = false;
    }

实现手段:通过改变循环的条件来终止循环,进而结束线程。

(2)缺点

1)该标志变量要定义在main方法外面,也就是全局变量;如果定义在里面,会被内部类捕获到,从而报错。

2)这种终止线程写法的缺点 ,即使循环条件不符合了,需要结束循环,但是由于上一次循环进入时,线程正在sleep中,导致无法即使终止线程。

2.2.第二种实现

(1)简介:使用Thread的三个方法来配合实现。下面介绍

|-------------------------------------------------|-------------------------|
| 方法 | 作用 |
| public void interrupt() | 修改标志位(哪个线程调用就修改) |
| public static native Thread currentThread() | 获取当前线程的引用 |
| public boolean isInterrupted() | 线程中自带的标志位,默认值为false |

(2)代码实现

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程结束了");
        });
        t.start();
        System.out.println("3s后结束线程");
        Thread.sleep(1000);
        t.interrupt();

    }

代码是结束了,但是抛出了异常。是因为唤醒sleep等阻塞的方法,即使线程在sleep中,也能即使结束线程(就是相比于第一种方法的优势)。设置成false之后,则会让sleep抛出一个InterruptedException异常,不需要等待sleep,进而结束了。

如果更改sleep抛出的异常,进而可以控制线程。

(3)提醒线程终止,但是不想结束

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e);
                    e.printStackTrace();
                }
            }
            System.out.println("线程结束了");
        });
        t.start();
        System.out.println("3s后结束线程");
        Thread.sleep(1000);
        t.interrupt();
    }

代码没有结束,抛出一个异常后继续执行了代码。

原因:在触发interruptd的时候,而线程正在sleep的时候,会唤醒正在sleep,并且把interrupt修改的标志位又修改回来(又改回false)。前面因为没有修改抛出的异常,而直接把线程终止了。

(4)提醒线程终止,马上终止

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e);
                    //e.printStackTrace();
                    System.out.println("自愿结束线程");
                    break;
                }
            }
            System.out.println("线程结束了");
        });
        t.start();
        System.out.println("3s后结束线程");
        Thread.sleep(1000);
        t.interrupt();
    }

(5)提醒线程终止,等会结束

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("我先打印完1234再结束");
                    System.out.println(1);
                    System.out.println(2);
                    System.out.println(3);
                    System.out.println(4);
                    break;
                }
            }
            System.out.println("线程结束了");
        });
        t.start();
        System.out.println("3s后结束线程");
        Thread.sleep(1000);
        t.interrupt();
    }
3.等待线程

等待线程有三个重载的方法,前面的两个比较常用

|---------------------------------------------|------------------------|
| 方法 | 说明 |
| public void join() | 等待线程结束(死等) |
| public void join(long millis) | 等待线程结束,最多等millis毫秒 |
| public void join(long millis,int nanos) | 同2,但可以更高精度(因此容易出错) |

线程等待的意义:多线程中,调度顺序是无序的,因此每个线程的执行时间和结束时间是不确定的。引用线程等待,就可以控制线程结束的先后顺序

下面以join()为主要介绍。

3.1.join()

(1)没有等待时

java 复制代码
 public static void main(String[] args)  {
        Thread t = new Thread(()->{
           while (true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println("t");
           }
        });
        t.start();
        System.out.println("main线程");
    }

此时,是main线程已经结束,但是t线程没有结束。

(2)引入等待

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
           while (true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println("t");
           }
        });
        t.start();
        t.join();
        System.out.println("main线程");
    }

通过代码的结果可以看到,没有执行后面的打印语句。这样的意思是:main线程需要等待t线程结束后才能结束,也就是结束t.join()语句才行。

此时,main线程在等待t线程,也就是等待状态。代码无法直接观看,借助调用栈来观看

(3)等待解析

哪个线程调用join,哪个线程就是要先执行结束的。准确的来说:在A线程里面,B线程调用join,那么线程结束的顺序就是 B-->A,也就是线程A会等待线程B先结束

(4)深入理解

上面的代码中,在main中调用t.join(),有三种情况。

1)如果线程t此时已经结束了,此时的join就会立即返回,相当于join没气作用

2)如果线程t还没结束,此时main线程就会阻塞等待t线程结束,直到t线程结束之后,join才会解除阻塞,继续执行main

3)当执行t.join时,t线程还没开始,就会直接跳过t.join(),相当于没起到作用

下面介绍上面三种情况,顺便多介绍两种情况。

第一种情况:t.join时,t线程已经结束了(只有t线程和main线程)

当代码执行到t.join()时,t线程已经被销毁了,所以执行join时就直接返回了,相当于没有

第二种情况:正常起到作用(只有t线程和main线程)

当执行到t.join()时,t线程还没结束,此时就会main线程就会阻塞等待,直到t线程结束之后才会继续执行

第三种情况:t线程还没开始(只有t线程和main线程)

这就是当执行到t.join()时,t线程还没开始执行

第四种情况:三个线程嵌套等待(t1线程、t2线程、main线程)

预期结束顺序:t2 -> t1 -> main

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(500);//确保t2线程此时已经启动
                t2.join();//t1线程阻塞等待t2线程结束后再执行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            for (int i = 0; i < 3; i++) {
                System.out.println("t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();

        t1.join();//main线程阻塞等待t1线程结束再继续执行
        System.out.println("main end");
    }

得出一个小结论:在A线程中使用B.join(),那么一定是B线程先结束,A线程后结束(join阻塞A线程);但是A线程中同时使用B.join()和C.join(),B线程和C线程之间是并发执行,A阻塞等待的时间是Math.max(B线程的时间,C线程的时间)

执行结果:

第五种情况:main线程也可以join()操作

要想做到main线程先结束,就要拿到主线程的引用

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Thread mainT = Thread.currentThread();//拿到main线程的引用
        System.out.println(mainT.getName());

        Thread t = new Thread(()->{
            try {
                mainT.join();//main线程先结束,t线程才会继续执行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t end");
         });
        t.start();
        Thread.sleep(2000);
        System.out.println("main end");
    }

由此可见,main线程也是可以join的

由于join()是一个死等的方法,只有t线程不结束,还没的代码就不会执行。这种写法很不友好。

3.2.join(long millis)

(1)这是一种带有时间的等待,参数就是需要等待的时间,如果超过了这个时间就不会继续执行下去,进而去执行后面的代码。

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
           while (true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               System.out.println("t");
           }
        });
        t.start();
        t.join(3000);
        System.out.println("main end");
    }

t线程是一个死循环线程,永远不会结束,如果是死等的join,那么main线程永久不会结束。但是这是一个带有时间的超时等待,过了这个时间就不会再继续等待下去

(2)如果join等待3秒,而t线程执行2秒,也会提前结束join,并不是真的会等待3秒

4.方法小结(超级重要)

注:方法括号中的ms代表毫秒

|---------------|-----------------------------------|------------------------|
| 方法名称 | 方法 | 方法作用 |
| 开始一个线程 | start() | 自定义的线程调用后,才代表该线程启动 |
| 获取当前线程的引用 | currentThread() | 可以获取当前线程的一个引用 |
| 休眠线程 | sleep(ms) | 让当前线程休眠指定时间 |
| 中断线程 | isInterrupted()、interrupted() | 提醒线程是否要终止 |
| 等待线程 | join()--无参方法 | 调用该方法的线程提前结束,阻塞 |
| | join(ms)--带有参数方法 | 在嵌套该线程的那个线程 |

二、线程状态

线程的状态,也是PCB中的状态。粗分的话有四个状态,细分是六个状态。下面我们以六个状态来介绍。

|-------------------|---------------|
| 状态 | 名字 |
| NEW | 新建状态 |
| TERMINATED | 终止态 |
| RUNNABLIE | 就绪态 |
| WAITING | 死等状态 |
| TIMED_WAITING | 带有超时时间的等待 |
| BLOCKED | 因锁竞争的阻塞 |

后面三种,大的方向都是阻塞状态

下面每个状态一一介绍,产生状态的原因。每种状态都会用代码使用,和在调用堆栈中观察,如果可以使用代码观察(使用getState()查看线程状态),也会一起展示。

1.NEW

(1)小写:new 中文名字:新建态

(2)产生时间/原因:在Thread对象产生后,为start()之前

(3)线程状态解释:此时的线程并未真正的产生,只存在硬盘中,当start()后,线程才真正产生(处于cpu上)

(4)代码观察状态

java 复制代码
public static void main(String[] args) {
       Thread t = new Thread();
       System.out.println(t.getState());
}

(5)使用调用堆栈观看

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("ttttt");
        System.out.println(t.getState());
        Thread.sleep(30000);
    }

让main线程休眠30秒,好让观察

此时,只能看到main线程,而看不到ttttt线程,原因就是此时只是创造出Thread对象,线程处于新建态,还未调度到cpu上执行

2.TERMINATED

(1)小写:terminated 中文:终止态

(2)产生原因:线程开始后,线程完成了任务之后,线程从pcb中被销毁

(3)代码观察

1)第一种

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            System.out.println("执行完这个语句t线程就结束");
        });
        System.out.println(t.getState());
        t.start();
        Thread.sleep(3000);
        System.out.println(t.getState());
    }

等待三秒后再去获取t线程的状态

2)第二种实现

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            System.out.println("执行完这个语句t线程就结束");
        });
        System.out.println(t.getState());
        t.start();
        t.join();
        System.out.println(t.getState());
    }

使用t.join(),就可以保证在t线程结束之后再去执行后面的语句(推荐)

(4)使用调用堆栈观察

使用堆栈无法观察,因为这个状态是线程终止态,线程已经被销毁了,不复存在。

3.RUNNABLE

(1)小写:runnable 中文:就绪状态

(2)产生原因有两种

1)这个线程正在CPU上执行

2)这个线程不在CPU上执行,但是可以随时调度到CPU上执行

下面我们只去研究第一种,因为第二种我们无法干预,也无法展示

(3)代码观察

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
    }

此时线程还是休眠中,任务还没结束,因此展示出来的就是RUNNABKE

(4)在调用堆栈中

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
           while (true) {

           }
        });
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
    }

在t线程中,我们使用死循环的写法。如果是休眠时间,但调用堆栈中会是:带有超时时间的等待状态。

4.WAITING

(1)小写:waiting 名称:等待

(2)原因:由于死等进入的等待状态。其中有两个典型的例子

1)由于join()进入的死等状态

2)由于wait()进入的死等状态(在线程安全问题部分会有学习)

(3)因join()进入的WAITING状态

由于要执行完t.join()才会去执行后面的语句,所以代码层面观察不到线程的状态

java 复制代码
 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("1");
            }
        });
        t.start();
        t.join();
        System.out.println(Thread.currentThread().getState());

    }

使用调用堆栈观察:

(4)因wait()进入的等待状态

这个方法居然做什么的,目前先不做具体的解释

可见,线程的WAITING状态是由于两种死等的方法造成的

5.TIMED_WAITING

(1)名称:带有超时时间的等待

(2)产生原因:由带参数的:join()、sleep()、waiting()方法而进入的带有超时时间的等待。

(3)第一种:由sleep()进入的等待

(4)第二种:带有时间的join()

(5)第三种:带有时间的wait()

以上就是进入TIMED_WAITING状态的三种方式

6.BLOCKED

(1)原因由来:由于锁竞争产生的阻塞

(2)代码展示

java 复制代码
public static void main(String[] args) {
        String str = "520";
        Thread t1 = new Thread(()->{
            synchronized (str) {
                while (true) {
                }
            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            synchronized (str) {
                System.out.println("1");
            }
        });
        t2.start();
        System.out.println(t2.getState());
    }

由于t1线程已经对str对象上锁,当t2再次想对str对象加锁时,就会产生互斥,从而进入阻塞状态。

7.状态总结(超级重要)

7.1.部分总结

(1)RUNNABLE状态

原因1:这个线程正在cpu上执行,也就是线程正在执行

原因2:这个线程暂时未在cpu上执行,但是可以随时调度到cpus上执行

(2)阻塞状态

阻塞状态的作用:进入阻塞状态的线程,暂时不能调度到cpu上执行,直到满足一定的条件,才会重新进入就绪状态。

阻塞状态有三个:WAITING、TIMED_WAITING、BLOCKED

三种状态都是阻塞状态,但是产生的原因不一样

7.2.表格总结

状态 产生原因1 产生原因2 产生原因3
NEW Thread对象创建时
TERMINATED 内核中的线程(PCB)被销毁(线程结束时)
RUNNABLE 线程正在cpu上运行时 线程可以随时调度到cpu运行(不可干预)
WAITING join() wait()
TIMED_WAITING sleep(参数) join(参数) wait(参数)
BLOCKED 对同一个锁竞争

相关推荐
路在脚下@17 分钟前
spring boot的配置文件属性注入到类的静态属性
java·spring boot·sql
森屿Serien20 分钟前
Spring Boot常用注解
java·spring boot·后端
苹果醋31 小时前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
Hello.Reader2 小时前
深入解析 Apache APISIX
java·apache
菠萝蚊鸭2 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0072 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
∝请叫*我简单先生2 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
ssr——ssss3 小时前
SSM-期末项目 - 基于SSM的宠物信息管理系统
java·ssm
一棵星3 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言