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

简介: 面试官:聊聊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,这时需要定位是否内存溢出或者增加资源来解决
  • 系统遇到了瞬间的大流量,这时我们可以启动限流机制或者服务降级来解决


面试官:恭喜你,通过了

相关文章
|
1天前
|
安全 Java 大数据
Java多线程编程:深入理解与应用
Java多线程编程:深入理解与应用
|
1天前
|
安全 Java 数据安全/隐私保护
Java中的多线程编程:基础知识与实践
【5月更文挑战第24天】 在现代软件开发中,多线程编程是提升应用性能和响应速度的关键技术之一。Java 作为一种广泛使用的编程语言,其内置的多线程功能为开发者提供了强大的并发处理能力。本文将深入探讨 Java 多线程的基础概念、实现机制以及在实际开发中的应用。我们将从线程的创建和管理出发,逐步讲解同步机制、死锁问题以及如何利用高级并发工具有效地构建稳定、高效的多线程应用。通过理论结合实例的方式,旨在帮助读者掌握 Java 多线程编程的核心技能,并在实际项目中灵活运用。
|
1天前
|
缓存 安全 Java
JAVA多线程编程与并发控制
```markdown Java多线程编程与并发控制关键点:1) 通过Thread或Runnable创建线程,管理线程状态;2) 使用synchronized关键字和ReentrantLock实现线程同步,防止数据竞争;3) 利用线程池(如Executors)优化资源管理,提高系统效率。并发控制需注意线程安全,避免死锁,确保程序正确稳定。 ```
|
1天前
|
安全 Java 开发者
Java多线程同步方法
【5月更文挑战第24天】在 Java 中,多线程同步是保证多个线程安全访问共享资源的关键。Java 提供了几种机制来实现线程间的同步,保证了操作的原子性以及内存的可见性。
10 3
|
2天前
|
SQL 存储 Java
致远互联java实习生面试
致远互联java实习生面试
19 0
|
2天前
|
缓存 监控 Java
深入理解Java并发编程:线程池的设计与实现
【5月更文挑战第23天】在现代软件开发中,多线程与并发编程是提高程序性能、响应速度和资源利用率的关键手段。Java语言提供了丰富的并发工具,其中线程池是管理线程资源、减少创建销毁开销、提高系统吞吐量的重要组件。本文将深入探讨线程池的核心概念、设计原理以及Java中的实现机制,帮助开发者更好地理解和应用线程池技术。
|
2天前
|
Java
java面试基础 -- 普通类 & 抽象类 & 接口
java面试基础 -- 普通类 & 抽象类 & 接口
9 0
|
2天前
|
存储 安全 Java
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
8 0
|
2天前
|
Java
java面试基础 -- 方法重载 & 方法重写
java面试基础 -- 方法重载 & 方法重写
6 0
|
2天前
|
安全 Java 调度
Java多线程- synchronized关键字总结
Java多线程- synchronized关键字总结
12 0