并发编程-Thread

并发编程——Thread

线程、进程、CPU

要想研究并发编程首先得了解线程、进程、CPU他们三个之间的联系

  1. 线程:线程是操作系统调度的最小执行单位,是程序执行的一条执行路径。一个进程可以包含多个线程,它们共享同一进程的上下文和资源。线程之间可以并发执行,通过上下文切换来实现多个线程之间的切换。
  2. 进程:进程是操作系统中的一个运行实例,它具有独立的内存空间和系统资源。一个进程可以包含多个线程,这些线程共享同进程的上下文和资源。进程之间相互独立,有独立的内存空间和系统资源,通过操作系统的调度来实现进程之间的切换。
  3. CPU:CPU(中央处理器)是计算机的主要执行部件,负责执行指令和处理计算任务。线程和进程都需要依赖CPU来执行指令和完成计算任务。操作系统通过调度算法来管理和分配CPU资源,将CPU的时间片分配给不同的进程或线程进行执行。

可以简单的理解为 一个进程里边包含多个线程,进程代表的就是电脑上启动的软件,统一都是由cpu通过切换时间片来进行调度。再举个例子,进程就好比一个工厂里的生产线,它有生产任务。线程就好比生产线上的工人,它们一起协作才能完成生产任务。每个工人(线程)干的活也不一样。那cpu就像老板,将不同的工序分配给不同的工人。总的来说通过合理地组织进程和线程的分工,并充分利用CPU的计算能力,可以有效地提高计算机的处理效率。

java中线程的使用

java中的线程使用非常简单,代码如下

extends Thread

public class ThreadOne extends Thread {
    public static void main(String[] args) {
        Thread t = new ThreadOne();
        t.start();
    }
    @Override
    public void run() {
        System.out.println("执行任务");
    }
}

implements Runnable

public class ThreadTwo implements Runnable {
    public static void main(String[] args) {
        ThreadTwo t = new ThreadTwo();
        new Thread(t).start();
    }
    @Override
    public void run() {
        System.out.println("执行任务");
    }
}

线程的工作原理

我们已经了解了线程、进程、CPU之间的关系,也知道了在java中线程的使用,那么线程时怎么执行的,或者说在JVM中是怎么体现的呢

这个地方只能进行推导一下加深理解(因为具体会涉及到操作系统底层C++代码,看不懂超纲了)

  1. 假设我们通过 new Thread().start() 来执行一个线程,那么它肯定是在操作系统底层创建一个线程,然后再去执行start()方法,Java里面的Thread类在JVM上对应的文件是thread.cpp。在thread.cpp文件中定义了很多和线程相关的方法,Thread.java类中的native方法就是在thread.cpp中实现的。

  2. 创建完线程调用start()方法后线程并不是立即执行,而是处在一个就绪状态,等着CPU来进行调度(就好比新员工刚来公司等着领导安排任务)

  3. 通过一系列调度算法来确定让哪个线程来执行,比如说刚好轮到 我们的 new Thread() 这个线程 ,回调它的run() 方法

    大致含义如下图所示
    在这里插入图片描述

线程的状态(生命周期)

线程的执行有开始就会有结束,我们也都知道run()方法执行完,线程就终止了,那么问题来了,在这个过程中线程会有哪些状态,首先我们通过Thread源码中可以看到线程的6种状态

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

NEW(初始)

初始化的状态就不用详细说了 new Thread() 线程被构建

RUNNABLE(运行)

运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中

BLOCKED(阻塞)

阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞 也分为几种情况

  1. 等待阻塞:运行的线程执行wait方法,jvm会把当前线程放入到等待队列
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 jvm会把当前的线程放入到锁池中
  3. 其他阻塞:运行的线程执行Thread.sleep或者t.join方法,或者发出了I/O请求时,JVM会把 当前线程设置为阻塞状态,当sleep结束、join线程终止、io处理完毕则线程恢复

WAITING(等待)

等待状态

TIMED_WAITING(超时等待)

超时等待状态,超时以后自动返回

TERMINATED(终止)

终止状态,表示当前线程执行完毕

以上就是线程的6种状态以及在调用指定方法时线程对应的状态,总结一下可以用一张图来表示
在这里插入图片描述

用代码来理解线程的状态

接下来通过一段代码来演示一下线程的状态

public class ThreadStatus {
    public static void main(String[] args) {
        //运行状态
         new Thread(()->{
             while (true){
             }
         },"RUNNABLE(运行)").start();
        //超时等待
         new Thread(()->{
             while(true){
                 try {
                     TimeUnit.SECONDS.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         },"TIME_WAITING(超时等待)").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 BlockedDemo(),"BlockA").start();
        new Thread(new BlockedDemo(),"BlockB").start();
    }
    static class BlockedDemo extends Thread{
        @Override
        public void run(){
            synchronized (BlockedDemo.class){
                while(true){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

通过命令 jps找到 ThreadStatus 的PID 执行 jstack PID 打印堆栈信息可以看到

E:\demo>jps
2176 ThreadStatus
4708 Launcher
7700 ComprehensiveLaboratoryApplication
2572
7388 Launcher
9548 Jps

E:\demo>jstack -l 2176
"WAITING(等待)" #14 prio=5 os_prio=0 tid=0x000002266775f000 nid=0x2f24 in Object.wait() [0x000000323c8ff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076bcfe678> (a java.lang.Class for com.zf.osce.infrastructure.implTese.ThreadStatus)
        at java.lang.Object.wait(Object.java:502)
        at com.zf.osce.infrastructure.implTese.ThreadStatus.lambda$main$2(ThreadStatus.java:29)
        - locked <0x000000076bcfe678> (a java.lang.Class for com.zf.osce.infrastructure.implTese.ThreadStatus)
        at com.zf.osce.infrastructure.implTese.ThreadStatus$$Lambda$3/1212899836.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
        
"TIME_WAITING(超时等待)" #13 prio=5 os_prio=0 tid=0x0000022667574800 nid=0x34bc waiting on condition [0x000000323c7fe000]
   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.zf.osce.infrastructure.implTese.ThreadStatus.lambda$main$1(ThreadStatus.java:17)
        at com.zf.osce.infrastructure.implTese.ThreadStatus$$Lambda$2/1973336893.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
        
"RUNNABLE(运行)" #12 prio=5 os_prio=0 tid=0x0000022667573800 nid=0x2540 runnable [0x000000323c6fe000]
   java.lang.Thread.State: RUNNABLE
        at com.zf.osce.infrastructure.implTese.ThreadStatus.lambda$main$0(ThreadStatus.java:9)
        at com.zf.osce.infrastructure.implTese.ThreadStatus$$Lambda$1/1334729950.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

线程的终止

接下来就要聊一聊线程的终止。

首先来屡屡思路

  1. 过去都是用
//过时的方法
new Thread().stop();
new Thread().suspend();

​ 强制执行,也就说甭管你执行到什么程度,直接给你停掉,和 linux中的kill -9 是类似的。

  1. 那正常情况下应该是怎么停止呢

(1)不再接收新的请求了

(2)把之前接收的请求都处理完

(3)最后停止

  1. 停止的方式

    run() 方法执行结束,线程停止

    有没有个友好的方式来停止。 就好比老师在课堂上讲课,突然有学生不举手就发言,老师话都没说到一半,就被打断,相当的不友好。那学生是不是应该举手示意老师一下,由老师来自主决定是否中断一下,让学生来进行发言。这就是友好的进行中断,由此引出一个方法

interrupt方法

  1. 根据刚才提到的例子,如果让我们自己来实现这个学生举手示意打断老师讲课该怎么实现。常规做法是不是

    boolean flag = false;
    //当学生举手示意时改为true
        flag = true;
    //老师根据flag状态来判断是否需要暂停下给学生讲解一下
    if(flag = true){
       ...... 
    }
    

    同理,interrupt( ) 是不是也这么做的,接下来我们用代码来感受一下

    public class InterruptedDemo implements Runnable{
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new InterruptedDemo());
            t1.start();
        }
    
        @Override
        public void run() {
            //Thread.currentThread().isInterrupted() 获取当前的中断状态
            System.out.println("学生是否举手示意"+Thread.currentThread().isInterrupted());
        }
    }
    

    我们得到当前的中断状态默认为false,此时没有进行任何的中断

在这里插入图片描述

我们假设 线程t1 为老师,main方法相当于学生,学生要举手示意是不是就相当于向老师发送中断请求,由老师来决定是不是回答学生的问题,代码行如下的改进

public class InterruptedDemo implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new InterruptedDemo());
        t1.start();
        //为了模拟演示效果 让t1线程充分执行
        Thread.sleep(5000);
        //调用interrupt() 修改中断状态 相当于学生举手示意
        t1.interrupt();
    }

    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //老师响应中断,老师自己做决定,是继续往下讲,还是直接下课
                //catch中的作用 1.唤醒阻塞的线程 2.中断复位
                e.printStackTrace();
            }
            System.out.println("老师"+Thread.currentThread().getName()+ "正在讲课");
        }

    }
}

运行结果如下
在这里插入图片描述
补充一点 interrupt() 源码有明显的注释说明

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag  只是为了设置中断标志
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

关于dump日志

如果线程在执行过程中出现问题,应该怎么去排查 用以下三个点来进行举例

  1. cpu占用率高,接口响应很慢

  2. cpu占用率不高,接口响应很慢

  3. 线程出现死锁

    注:所有模拟场景都是在linux 环境下

cpu占用率高,接口响应很慢

先上代码

@RestController
@RequestMapping("/thread")
public class LockController {
    /**
     * 模拟 cpu占用高,响应很慢 场景
     */
    @GetMapping("/test1")
    public String getResult(){
        Thread t1 = new Thread(new ThreadAlways());
        t1.start();
        try {
            //让 t1 线程执行完,营造响应很慢的效果
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return "cpu占用高,响应很慢";
    }
    class ThreadAlways implements Runnable{
        @Override
        public void run() {
            while (true){
                try {
                    //sleep 1秒 减少点日志打印
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("一直在执行");
            }
        }
    }
}

将以上的代码进行打包 demo-0.0.1-SNAPSHOT.jar

运行 jar包

[root@localhost servers]# nohup java -jar demo-0.0.1-SNAPSHOT.jar > demo.log &

通过 top -c 命令找到占用CPU最高的进程 ,找到它的PID 7647

[root@localhost servers]# top -c
top - 15:44:48 up  7:09,  1 user,  load average: 0.60, 0.22, 0.12
Tasks: 212 total,   1 running, 211 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.8 us, 11.5 sy,  0.0 ni, 84.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1863020 total,    87732 free,   999404 used,   775884 buff/cache
KiB Swap:  2097148 total,  2096884 free,      264 used.   680312 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND           
  7647 root      20   0 4399352 301636  13368 S 120.3 16.2   1:09.23 java -jar demo-0.+
    67 root      20   0       0      0      0 S   2.7  0.0   0:00.23 [kswapd0]         
  5378 root      20   0       0      0      0 S   2.7  0.0   0:00.61 [kworker/u256:0]  
   239 root      20   0       0      0      0 S   0.3  0.0   0:00.56 [kworker/7:2]     
   372 root      20   0       0      0      0 S   0.3  0.0   0:26.97 [kworker/1:2]     
  7793 root      20   0  162132   2432   1668 R   0.3  0.1   0:00.06 top -c            
     1 root      20   0  194220   7308   4196 S   0.0  0.4   0:03.91 /usr/lib/systemd/+
     2 root      20   0       0      0      0 S   0.0  0.0   0:00.06 [kthreadd]        
     4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 [kworker/0:0H]    
     6 root      20   0       0      0      0 S   0.0  0.0   0:00.02 [ksoftirqd/0]  

然后再定位到对应的线程, top -H -p 80972 查找到该进程中最消耗CPU的线程 PID 7792

[root@localhost servers]# top -H -p 7674
top - 15:47:52 up  7:12,  1 user,  load average: 0.64, 0.44, 0.22
Threads:  38 total,   1 running,  37 sleeping,   0 stopped,   0 zombie
%Cpu(s):  4.4 us,  8.2 sy,  0.0 ni, 87.3 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem :  1863020 total,    82912 free,   910256 used,   869852 buff/cache
KiB Swap:  2097148 total,  2049780 free,    47368 used.   775804 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
  7792 root      20   0 4465916 276072   3140 R 99.0 14.8   3:46.80 Thread-4        

通过 printf “0x%x\n” 7792 命令,把对应的线程PID转化为16进制

[root@localhost servers]# printf "0x%x\n" 7792
0x1e70

执行命令 jstack 7647| grep -A 20 0x1e70查看线程Dump日志,其中-A 20表示 展示20行, 7647表示进程ID, 0x1e70表示线程ID

[root@localhost servers]#  jstack 7647 | grep -A 20 0x1e70
"Thread-4" #32 daemon prio=5 os_prio=0 tid=0x00007f9458140000 nid=0x1e70 runnable [0x00007f942fffe000]
   java.lang.Thread.State: RUNNABLE
	at java.io.FileOutputStream.writeBytes(Native Method)
	at java.io.FileOutputStream.write(FileOutputStream.java:326)
	at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
	- locked <0x00000000e3bbd770> (a java.io.BufferedOutputStream)
	at java.io.PrintStream.write(PrintStream.java:482)
	- locked <0x00000000e3bbd750> (a java.io.PrintStream)
	at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
	at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
	at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
	- locked <0x00000000e3bbd840> (a java.io.OutputStreamWriter)
	at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
	at java.io.PrintStream.newLine(PrintStream.java:546)
	- eliminated <0x00000000e3bbd750> (a java.io.PrintStream)
	at java.io.PrintStream.println(PrintStream.java:807)
	- locked <0x00000000e3bbd750> (a java.io.PrintStream)
	at com.example.demo.controller.LockController$ThreadAlways.run(LockController.java:20)
	at java.lang.Thread.run(Thread.java:748)

在dump日志中找到你看着最眼熟的,由此我们知道了LockController 中的ThreadAlways.run方法一直在执行

CPU占用不高,接口相应很慢

依旧是先上代码

 /**
     * 模拟 cpu占用不高,响应很慢 场景
     */
    @GetMapping("/test2")
    public String test2(){
        Thread t1 = new ThreadA();
        Thread t2 = new ThreadB();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
        try {
            //调用join是为了体现出响应慢的效果,因为是异步执行,不加会直接return结果
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "cpu占用不高,响应很慢";
    }

   public class ThreadA extends Thread {
        @Override
        public void run() {
            System.out.println("================A===================");
            synchronized (ThreadA.class) {
                System.out.println("我要开始执行任务A。。。。" + Thread.currentThread().getName());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (ThreadB.class) {
                }
                System.out.println("我在执行任务结束了A。。。。" + Thread.currentThread().getName());
            }
        }
    }
   public class ThreadB extends Thread{
    @Override
    public void run() {
        System.out.println("================B===================");
        synchronized (ThreadB.class) {
            System.out.println("我要开始执行任务B。。。。" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (ThreadA.class) {
            }
            System.out.println("我在执行任务结束了B。。。。" + Thread.currentThread().getName());
        }
    }
   } 

访问test2接口会一直等待,因为两个线程在互相抢占锁造成死锁。检查办法 是 jps 找到这个接口所在服务的pid 通过jstack命令我们可以看到如下结果

Found one Java-level deadlock:
=============================
"线程2":
  waiting to lock monitor 0x00007feacc0062c8 (object 0x00000000f7b9ddf0, a java.lang.Class),
  which is held by "线程1"
"线程1":
  waiting to lock monitor 0x00007fea60002318 (object 0x00000000f7ba18f8, a java.lang.Class),
  which is held by "线程2"

Java stack information for the threads listed above:
===================================================
"线程2":
	at com.example.demo.application.ThreadB.run(ThreadB.java:15)
	- waiting to lock <0x00000000f7b9ddf0> (a java.lang.Class for com.example.demo.application.ThreadA)
	- locked <0x00000000f7ba18f8> (a java.lang.Class for com.example.demo.application.ThreadB)
"线程1":
	at com.example.demo.application.ThreadA.run(ThreadA.java:15)
	- waiting to lock <0x00000000f7ba18f8> (a java.lang.Class for com.example.demo.application.ThreadB)
	- locked <0x00000000f7b9ddf0> (a java.lang.Class for com.example.demo.application.ThreadA)

Found 1 deadlock.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值