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


面试官:恭喜你,通过了

相关文章
|
16天前
|
Java 调度
Java线程的六种状态
Java线程有六种状态: 初始(NEW)、运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)、终止(TERMINATED)。
37 1
|
2天前
|
Java 程序员 调度
Java中的多线程编程:概念、实现及性能优化
【5月更文挑战第85天】本文主要探讨了Java中的多线程编程,包括其基本概念、实现方式以及如何进行性能优化。首先,我们将介绍多线程的基本概念,然后详细讨论如何在Java中实现多线程,包括继承Thread类和实现Runnable接口两种方式。最后,我们将探讨一些提高多线程程序性能的策略,如使用线程池和减少同步开销等。
|
2天前
|
监控 Java 开发者
深入理解Java并发编程:线程池的原理与实践
【5月更文挑战第85天】 在现代Java应用开发中,高效地处理并发任务是提升性能和响应能力的关键。线程池作为一种管理线程的机制,其合理使用能够显著减少资源消耗并优化系统吞吐量。本文将详细探讨线程池的核心原理,包括其内部工作机制、优势以及如何在Java中正确实现和使用线程池。通过理论分析和实例演示,我们将揭示线程池对提升Java应用性能的重要性,并给出实践中的最佳策略。
|
1天前
|
安全 Java 开发者
掌握Java并发编程:线程安全与性能优化之道
在多核处理器普及的今天,充分利用并发编程技术是提升应用性能的关键。本文将深入探讨Java中的并发编程,从基本概念到高级技巧,揭示如何通过正确的同步机制和锁策略来确保线程安全,同时避免常见的并发陷阱。我们将一起探索高效利用线程池、减少锁竞争、以及使用现代并发工具类等方法,以达到性能的最优化。
|
3天前
|
安全 Java 数据处理
Java并发编程:线程同步与协作的深度解析
在探索Java并发编程的海洋中,线程同步与协作的灯塔指引着航向。本文将深入挖掘线程同步机制的核心原理,揭示锁、条件变量等工具如何确保数据的一致性和线程间有序的通信。通过案例分析,我们将解码高效并发模式背后的设计哲学,并探讨现代Java并发库如何简化复杂的同步任务。跟随文章的步伐,您将获得提升多线程应用性能与可靠性的关键技能。 【7月更文挑战第24天】
18 5
|
2天前
|
Java
如何在Java中实现线程池?
在Java中,线程池是高效管理线程的关键机制,避免了无限制创建线程的资源浪费和系统不稳定。通过`Executor`和`ExecutorService`接口,代码与具体线程池实现解耦,提供灵活性。`Executors`类简化线程池创建,适合基本需求
|
1天前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
4 1
|
2天前
|
Java
如何在Java中实现多线程的Socket服务器?
在Java中,多线程Socket服务器能同时处理多个客户端连接以提升并发性能。示例代码展示了如何创建此类服务器:监听指定端口,并为每个新连接启动一个`ClientHandler`线程进行通信处理。使用线程池管理这些线程,提高了效率。`ClientHandler`读取客户端消息并响应,支持简单的文本交互,如发送欢迎信息及处理退出命令。
|
3天前
|
Java
如何使用jstack命令查看Java进程的线程栈
如何使用jstack命令查看Java进程的线程栈?
11 2
|
5天前
|
SQL Java Unix
Android经典面试题之Java中获取时间戳的方式有哪些?有什么区别?
在Java中获取时间戳有多种方式,包括`System.currentTimeMillis()`(毫秒级,适用于日志和计时)、`System.nanoTime()`(纳秒级,高精度计时)、`Instant.now().toEpochMilli()`(毫秒级,ISO-8601标准)和`Instant.now().getEpochSecond()`(秒级)。`Timestamp.valueOf(LocalDateTime.now()).getTime()`适用于数据库操作。选择方法取决于精度、用途和时间起点的需求。
19 3