面试官:聊聊java中线程的生命周期

面试官:你好,聊一聊java中线程的生命周期?
我:在java的Thread类中,定义了一个名字叫State的枚举类,里面有6个状态,NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,这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状态?
我:如下面2段代码,我分别用Runnable接口和Thread类来创建线程,如果不调用run()方法和start()方法,线程就是处于NEW状态。而调用了这2个方法,线程就进入了Runnable状态。

//实现Runnable接口创建线程
((Runnable) () -> 
        System.out.println("I am thread1")
).run();
//用new Thread创建线程
new Thread(
        () -> System.out.println("I am thread2")
).start();

面试官:调用了run()方法和start()方法,线程就一定会进入Runnable状态吗?
我:不一定,当多个线程同时对一个共享变量进行修改时,为了线程安全,我们会对共享变量加锁,如果当前线程没有获取到锁,就只能进行等待,这时线程转成了BLOCKED的状态。

面试官:线程的状态什么时候会转入WAITING呢?
我:从线程状态的定义中我们能看到,调用以下3个方法,线程会进入WAITING状态:

  • Object.wait,当前线程获取到锁后,调用wait方法会释放掉锁进入WAITING状态等待其他线程唤醒。
  • Thread.join,如果有一个线程thread1,当调用thread1.join方法时,当前线程进入WAITING状态等thread1执行结束后转入Runnable状态继续执行
  • LockSupport.park(thread1),调用这个方法后thread1线程会转入WAITING状态,等调用了LockSupport.unpark(thread1)方法后thread1转入Runnable状态

面试官:线程的状态什么时候会转入TIMED_WAITING呢?
我:上面的3个方法都有对应的带时间参数的方法,还有一个sleep方法,调用这些方法都,线程状态都会进入TIMED_WAITING,具体如下:

Thread.sleep(long millis)
Object.wait(long timeout)
Thread.join(long millis)
LockSupport.parkNanos(long nanos)
LockSupport.parkUntil(long deadline)

面试官:那线程什么时候进入TERMINATED状态呢?
我:java运行结束后就进入了TERMINATED状态,这里有2种情况,一种是线程里面所有代码执行完成,另一个是线程某行代码出抛出了异常而结束。

面试官:如果线程竞争不到CPU使用权,这时还是Runnable状态吗?
我:JVM是不关心底层操作系统的状态的,即使线程获取不到CPU使用权,在JVM看来也是Runnable状态。

面试官:如果一个线程阻塞在IO等待上,比如同步调用一个阻塞式的API,这个等待IO响应的过程中线程的状态是Runnable还是WAITING?
我:从JVM来看,线程是Runnable的,因为JVM不关心操作系统的状态。但是从操作系统来看,这时线程是阻塞的,并且已经让出了CPU的使用权。这是CPU会挂起当前线程,由DMA完成IO操作,IO返回结果后DMA会通知CPU恢复当前线程的执行。

面试官:如果发现部署java应用的服务器CPU使用率很高,有什么好的定位方法吗?
我:如果服务器上部署多个应用,首先使用top命令查看,找到CPU使用率高的进程。如下我们定位到21504这个进程的CPU使用率很高:

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
21504 xxxx+  20   0 8125900 1.548g  14732 S 277.1 10.0  30:28.93 java                                                                                                                                           
6760 yyyy+  20   0 7905468 1.129g  14180 S   1.0  7.3   1:03.24 java 

定位到21504这个进程占用CPU很高后,我们使用下面命令找出这个java进程中占用CPU高的几个线程,如下:

ps -H -p 21504

结果如下:

  PID USER   PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+  COMMAND    
18674 xxxx+  20   0 8069992 1.313g   7664 S 16.6  8.5  10:21.42 java                                                                                                                                            
18614 xxxx+  20   0 8069992 1.313g   7664 S 16.3  8.5  10:23.06 java                                                                                                                                            
18617 xxxx+  20   0 8069992 1.313g   7664 S 15.9  8.5  10:23.06 java                                                                                                                                            
18618 xxxx+  20   0 8069992 1.313g   7664 S 15.3  8.5  10:21.97 java                                                                                                                                            
27303 xxxx+  20   0 8069992 1.313g   7664 S 15.3  8.5   8:08.09 java                                                                                                                                            
28738 xxxx+  20   0 8069992 1.313g   7664 S 15.3  8.5   8:00.73 java                                                                                                                                            
 2286 xxxx+  20   0 8069992 1.313g   7664 S 15.3  8.5   0:25.86 java                                                                                                                                            
29059 xxxx+  20   0 8069992 1.313g   7664 S 15.0  8.5   7:51.61 java                                                                                                                                            
 2609 xxxx+  20   0 8069992 1.313g   7664 S 15.0  8.5   0:16.94 java                                                                                                                                            
18610 xxxx+  20   0 8069992 1.313g   7664 S 14.6  8.5  10:23.84 java                                                                                                                                            
18680 xxxx+  20   0 8069992 1.313g   7664 S 14.6  8.5  10:22.27 java                                                                                                                                            
 2121 xxxx+  20   0 8069992 1.313g   7664 S 14.6  8.5   0:31.35 java                                                                                                                                            
 2164 xxxx+  20   0 8069992 1.313g   7664 S 14.6  8.5   0:29.22 java                                                                                                                                            
18613 xxxx+  20   0 8069992 1.313g   7664 S 14.3  8.5  10:22.32 java  

之后我们可以使用jstack命令打印出进程的堆栈信息,从中可以定位到这些线程的调用栈,命令如下:

jstack -l 21504

结果如下:

Thread 18674: (state = BLOCKED)
 - sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
 - java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=175 (Compiled frame)
 - java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=2039 (Compiled frame)
 - java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=24, line=1081 (Compiled frame)
 - java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=1, line=809 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.getTask() @bci=149, line=1067 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=26, line=1127 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=617 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=748 (Compiled frame)


Thread 18610: (state = IN_NATIVE)
 - java.net.SocketInputStream.socketRead0(java.io.FileDescriptor, byte[], int, int, int) @bci=0 (Compiled frame; information may be imprecise)
 - java.net.SocketInputStream.socketRead(java.io.FileDescriptor, byte[], int, int, int) @bci=8, line=116 (Compiled frame)
 - java.net.SocketInputStream.read(byte[], int, int, int) @bci=117, line=171 (Compiled frame)
 - java.net.SocketInputStream.read(byte[], int, int) @bci=11, line=141 (Compiled frame)
 - oracle.net.ns.Packet.receive() @bci=180, line=308 (Compiled frame)
 - oracle.net.ns.DataPacket.receive() @bci=1, line=106 (Compiled frame)
 - oracle.net.ns.NetInputStream.getNextPacket() @bci=48, line=324 (Compiled frame)
 - oracle.net.ns.NetInputStream.read(byte[], int, int) @bci=33, line=268 (Compiled frame)
 - oracle.net.ns.NetInputStream.read(byte[]) @bci=5, line=190 (Compiled frame)
 - oracle.net.ns.NetInputStream.read() @bci=73, line=107 (Compiled frame)
 - oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket() @bci=94, line=143 (Compiled frame)
 - oracle.jdbc.driver.T4CSocketInputStreamWrapper.read() @bci=18, line=80 (Compiled frame)
 - oracle.jdbc.driver.T4CMAREngine.unmarshalUB1() @bci=6, line=1137 (Compiled frame)
 - oracle.jdbc.driver.T4CTTIfun.receive() @bci=11, line=350 (Compiled frame)
 - oracle.jdbc.driver.T4CTTIfun.doRPC() @bci=63, line=227 (Compiled frame)
 - oracle.jdbc.driver.T4C7Ocommoncall.doOCOMMIT() @bci=7, line=75 (Compiled frame)
 - oracle.jdbc.driver.T4CConnection.doCommit(int) @bci=18, line=641 (Compiled frame)
 - oracle.jdbc.driver.PhysicalConnection.commit(int) @bci=132, line=3928 (Compiled frame)
 - oracle.jdbc.driver.PhysicalConnection.commit() @bci=5, line=3934 (Compiled frame)
 - com.zaxxer.hikari.pool.ProxyConnection.commit() @bci=4, line=361 (Compiled frame)
 - com.zaxxer.hikari.pool.HikariProxyConnection.commit() @bci=1 (Compiled frame)

从这些信息中,我们可以找到上面定位到占用CPU高的线程,从而找到对应的代码,进行分析。

我们也可以把上面找出的CPU高的线程转成16进制,用下面命令:

printf "%x\n" 18674

​上面命令打印出48f2,然后用下面的命令找出某个线程的堆栈信息,下面命令打印10行:

jstack -l 21504 | grep 48f2 -A 10

面试官:你遇到过哪些导致CPU飙升的场景呢?又是怎么解决的?
我:根据jstack定位到的源码问题,大概有以下几种情况:

  • 程序中有死循环,这时线程会一直占着CPU导致CPU飙升,用上面讲的方法定位到死循环并解决
  • 应用中打印了大量的日志,这时应该删除一些不必要的日志或者调高日志级别
  • 应用是计算密集型的,计算处理时间过长,这时候我们需要优化代码执行效率或者增加CPU资源
  • JVM内存快要用完或者操作系统不能给JVM分配足够内存,这时JVM会频繁地GC,这时需要定位是否内存溢出或者增加资源来解决
  • 系统遇到了瞬间的大流量,这时我们可以启动限流机制或者服务降级来解决

面试官:恭喜你,通过了 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君哥聊技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值