放一张线程的原理图:
java代码创建线程后,我们通过调用start()
方法启动线程,调用thread.cpp的方法来启动线程,底层还是通过操作系统的create_thread
和start_thread
方法来操作的线程
线程在运行过程中,会存在几种不同的状态,一般来说,在Java中,线程的状态一共是6种状态,分别是
NEW: 初始状态,线程被构建,但是还没有调用start方法
**RUNNABLED:**运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”
**BLOCKED:**阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞
也分为几种情况
WAITING: 等待状态
TIME_WAITING : 超时等待状态,超时以后自动返回
TERMINATED: 终止状态,表示当前线程执行完毕
Thread提供了线程的一些操作方法,比如stop、suspend等,这些方法可以终止一个线程或者
挂起一个线程,但是这些方法都不建议大家使用。因为这些方法都是强制中断的方法,线程任务执行了一半被强制中断,可能会导致数据产生问题。这种行为类似于在linux系统中执行 kill -9
命令,它是一种不安全的操作。
一般情形下线程是不需要用户手动去干预的, 哪些线程的中断需要外部干预呢?
public class ThreadA extends Thread {public void run() {while(true){System.out.println("ThreadA .... ");}}public static void main(String[] args) {ThreadA myThread1 = new ThreadA();myThread1.start();}
}
对于线程中有无限循环的情形,代码中必须要有一个结束条件,并且用户可以在其他地方能够修改这个结束条件从而让线程感知到变化。像上面的代码,我们把while(true)
改成while(flag)
,然后这个flag
作为共享变量可以被外部修改,修改之后使得不满足循环条件然后退出循环并且结束线程
Java里提供了一个 interrupt
方法,这个方法就是实现线程中断操作的,其实现原理就是通过修改线程的中断标识来实现线程中断
public class InterruptDemo {private static int i = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while(!Thread.currentThread().isInterrupted()){//默认情况下isInterrupted返回false、通过thread.interrupt变成了truei++;}System.out.println("final num:"+ i);},"InterruptDemo");thread.start();Thread.sleep(1000);thread.interrupt();}}
public class InterruptDemo {private static int i = 0;public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(()->{while(!Thread.currentThread().isInterrupted()){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {System.out.println("线程中断");e.printStackTrace();//当前异常只是响应了外部的中断命令,同时线程的中断状态也会复位,如果需要中断该线程,我们需要在这里再执行一次中断Thread.currentThread().interrupt();}}},"InterruptDemo");thread.start();Thread.sleep(1000);thread.interrupt();}}
我们平时在线程中使用的sleep、wait、join等操作,它都会抛出一个InterruptedException异常,为什么要抛出这个异常,是因为它在阻塞期间,必须要能够响应被其他线程发起的中断请求,而这个响应是通过InterruptedException 来实现的
InterruptedException异常的抛出并不意味着线程必须终止,而是提醒当前线程有中断的操作发生,至于接下来怎么处理取决于线程本身,比如
我们在使用线程的时候,如果出现问题,怎么排查?
比如说CPU占用率很高,响应很慢;CPU占用率不高,但响应很慢;线程出现死锁的情况
nohup java -jar -Dserver.port=8580 thread-demo-0.0.1-SNAPSHOT.jar > localhost.log &
出现死锁的时候就会有这个问题,因为两个线程彼此等待,所以虽然响应很慢,但是cpu并不高
@GetMapping("/dead")public String dumpDeadLock(){Thread a = new ThreadA();Thread b = new ThreadB();a.start();b.start();return "ok";}
public class ThreadA extends Thread {@Overridepublic void run() {synchronized (ThreadA.class) {System.out.println("thread A" + Thread.currentThread().getName());try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (ThreadB.class) {}}}
}
public class ThreadB extends Thread {@Overridepublic void run() {synchronized (ThreadB.class) {System.out.println("thread A" + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (ThreadA.class) {}}}
}
查看死锁问题的操作步骤如下:
jps
命令,查看java进程的pidjstack
查看线程日志Found one Java-level deadlock:
信息。只要找到这个信息就可以定位到问题。
如果线程中出现死循环,就会有这样的问题
@GetMapping("/loop")public String dumpWhile(){new Thread(new LoopThread()).start();return "ok";}
public class LoopThread implements Runnable {@Overridepublic void run() {while (true) {System.out.println("Thread run...");}}
}
排查cpu过高问题的步骤
top -c
该命令可以动态显示进程及占用资源的排行榜,从而找到占用CPU最高的进程PID ,假设为PID=89831top -H -p [PID]
查找到该进程中最消耗CPU的线程,得到PID2=88765printf "0x%x\n" 88765
把对应的线程PID转化为16进制jstack 89831| grep -A 30 0x15abd
查看线程Dump日志,其中-A 30表示展示30行, 89831表示进程ID, 0x15abd表示16进制的线程ID可以从打印的dump日志看出是哪个方法的执行逻辑导致的cpu过高
上一篇:JVM知识整理
下一篇:2023面试题汇总二