java多线程基础篇(一)java线程的状态

1、 多线程的基本原理

从线程的start方法,到操作系统运行一个线程,实际上底层做了什么?
在这里插入图片描述
OS调度算法有很多,比如先来先服务调度算法(FIFO)、最短优先(就是对短作业的优先调度)、时 间片轮转调度等。

2、线程的运行状态示例

示例代码

package com.lf.thread.status;

import java.util.concurrent.TimeUnit;

/**
 * Description: TODO
 * Author: ful
 * Date: Created in 2022/12/6 18:00
 */
public class ThreadStatus {

    public static void main(String[] args) {
        //TIME_WAITING
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "timeWaiting").start();

        //WAITING,线程在ThreadStatus类锁上通过wait进行等待
        new Thread(() -> {
            while(true){
                synchronized(ThreadStatus.class){
                    try {
                        ThreadStatus.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"Waiting").start();

        //线程在ThreadStatus加锁后,不会释放锁
        new Thread(new Blocked(),"BlockDemo-01").start();
        new Thread(new Blocked(),"BlockDemo-02").start();

    }

    static class Blocked implements Runnable{

        @Override
        public void run() {
            synchronized (Blocked.class){
                while (true){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

jps命令

运行main方法,点击类ThreadStatus右键点击->Open in->Terminal 打开终端
输入jps

D:\ideaWorkspace\lf-es-kafka-demo\src\main\java\com\lf\thread\status>jps
24256
31360 Jps
15492 ThreadStatus

jstack命令

找到ThreadStatus的pid 15492,输入jstack 15492 显示上面四个线程运行的状态

D:\ideaWorkspace\lf-es-kafka-demo\src\main\java\com\lf\thread\status>jstack 15492
2022-12-07 09:56:50
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):

"DestroyJavaVM" #24 prio=5 os_prio=0 tid=0x000000000281c000 nid=0x2ff4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"BlockDemo-02" #23 prio=5 os_prio=0 tid=0x0000000021478800 nid=0x62bc waiting for monitor entry [0x0000000022a8f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.lf.thread.status.ThreadStatus$Blocked.run(ThreadStatus.java:50)
        - waiting to lock <0x000000076cf058e0> (a java.lang.Class for com.lf.thread.status.ThreadStatus$Blocked)
        at java.lang.Thread.run(Thread.java:748)

"BlockDemo-01" #22 prio=5 os_prio=0 tid=0x0000000021214800 nid=0x646c waiting on condition [0x000000002298f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.lf.thread.status.ThreadStatus$Blocked.run(ThreadStatus.java:50)
        - locked <0x000000076cf058e0> (a java.lang.Class for com.lf.thread.status.ThreadStatus$Blocked)
        at java.lang.Thread.run(Thread.java:748)

"Waiting" #21 prio=5 os_prio=0 tid=0x000000002120f800 nid=0x7828 in Object.wait() [0x000000002288f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076c95f570> (a java.lang.Class for com.lf.thread.status.ThreadStatus)
        at java.lang.Object.wait(Object.java:502)
        at com.lf.thread.status.ThreadStatus.lambda$main$1(ThreadStatus.java:29)
        - locked <0x000000076c95f570> (a java.lang.Class for com.lf.thread.status.ThreadStatus)
        at com.lf.thread.status.ThreadStatus$$Lambda$2/290658609.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"timeWaiting" #20 prio=5 os_prio=0 tid=0x000000002120e800 nid=0x6fa8 waiting on condition [0x000000002278e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.lf.thread.status.ThreadStatus.lambda$main$0(ThreadStatus.java:17)
        at com.lf.thread.status.ThreadStatus$$Lambda$1/1908923184.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

3、java中线程的所有状态

通过上面这段代码可以看到,线程在运行过程中,会存在几种不同的状态,一般来说,在Java中,线程
的状态一共是6种状态,分别是

NEW:初始状态,线程被构建,但是还没有调用start方法
RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞
也分为几种情况:
Ø 等待阻塞:运行的线程执行wait方法,jvm会把当前线程放入到等待队列
Ø 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么
jvm会把当前的线程放入到锁池中
Ø 其他阻塞:运行的线程执行Thread.sleep或者t.join方法,或者发出了I/O请求时,JVM会把
当前线程设置为阻塞状态,当sleep结束、join线程终止、io处理完毕则线程恢复
WAITING: 等待状态
TIME_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕

4、线程的状态转换

状态转换图

5、线程的终止

如何正确停止一个线程呢?Thread提供了线程的一些操作方法,比如stopsuspend等,这些方法可以终止一个线程或者挂起一个线程,但是这些方法都不建议大家使用。

举个例子,假设一个线程中,有多个任务在执行,此时,如果调用stop方法去强行中断,那么这个时候相当于是发送一个指令告诉操作系统把这个线程结束掉,但是操作系统的这个结束动作完成不代表线程中的任务执行完成,很可能出现线程的任务执行了一般被强制中断,最终导致数据产生问题。这种行为类似于在linux系统中执行 kill -9类似,它是一种不安全的操作。

那么除了这种方法之外,还有什么方式可以实现线程的终止呢?要了解这个问题,我们首先需要知道,
一个线程什么情况下算是终止了。

线程的真正结束

package com.lf.thread.status;

public class MyThread extends Thread {

    public void run() {
        System.out.println("MyThread.run()");
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.start();
    }
}

在正常情况下,这个线程是不需要人为干预去结束的。如果要强制结束,只能走stop这个方法。
那在哪些情况下,线程的中断需要外部干预呢?

线程中存在无限循环执行,比如while(true)循环
线程中存在一些阻塞的操作,比如sleep、wait、join等。

存在循环的线程

假设存在如下场景,在run方法中,存在一个while循环,因为这个循环的存在使得这个run方法一直无
法运行结束,这种情况下,如何终止呢?

package com.lf.thread.status;

public class MyThread extends Thread {

    public void run() {
        while (true) {
            System.out.println("MyThread.run()");
        }
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.start();
    }
}

按照我们开发的思维来说,首先要解决的就是,while(true)这个循环,必须要有一个结束条件,其次是要在其他地方能够修改这个结束条件让该线程感知到变化。假设我们把while(true)改成while(flag),这个flag可以作为共享变量被外部修改,修改之后使得循环条件无法被满足,从而退出循环并且结束线程。

这段逻辑其实非常简单,其实就是给了线程一个退出的条件,如果没有这个条件,那么线程将会一直运
行。实际上,在Java提供了一个interrupt 方法,这个方法就是实现线程中断操作的,它的作用和上面讲的这个案例的作用一样。

interrupt方法

当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。
线程通过检查资深是否被中断来进行相应,可以通过isInterrupted()来判断是否被中断。

package com.lf.thread.status;

import java.util.concurrent.TimeUnit;

public class InterruptDemo {
    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                //默认情况下isInterrupted返回false、通过thread.interrupt变成了true
                i++;
            }
            System.out.println("Num:" + i);
        }, "interruptDemo");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt(); //加和不加的效果
    }
}

这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停
止,因此这种终止线程的做法显得更加安全和优雅

处于阻塞状态下的线程中断

另外一种情况,就是当线程处于阻塞状态下时,我想要中断这个线程,那怎么做呢?

package com.lf.thread.status;

import java.util.concurrent.TimeUnit;

public class InterruptBlockDemo {
    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Num:" + i);
        }, "InterruptBlockDemo");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }
}

运行抛出异常,并且线程未终止,还在运行

com.lf.thread.status.InterruptBlockDemo
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.lf.thread.status.InterruptBlockDemo.lambda$main$0(InterruptBlockDemo.java:12)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code -1

从这个例子中反馈出一个问题,我们平时在线程中使用的sleep、wait、join等操作,它都会抛出一个InterruptedException异常,为什么会抛出异常,是因为它在阻塞期间,必须要能够响应被其他线程发起中断请求之后的一个响应,而这个响应是通过InterruptedException来体现的。

但是这里需要注意的是,在这个异常中如果不做任何处理的话,我们是无法去中断线程的,因为当前的异常只是响应了外部对于这个线程的中断命令,同时,线程的中断状态也会复位,如果需要中断,则还需要在catch中添加下面的代码

package com.lf.thread.status;

import java.util.concurrent.TimeUnit;

public class InterruptBlockDemo {
    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); //再次中断
                }
            }
            System.out.println("Num:" + i);
        }, "InterruptBlockDemo");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
        System.out.println(thread.isInterrupted());
    }
}

所以,InterruptedException异常的抛出并不意味着线程必须终止,而是提醒当前线程有中断的操作发生,至于接下来怎么处理取决于线程本身,比如

1. 直接捕获异常不做任何处理
2. 将异常往外抛出
3. 停止当前线程,并打印异常信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值