目录
正文:
1.JConsole
JConsole是Java监控和管理控制台工具,是Java Development Kit(JDK)中自带的一个图形化工具,用于监视和管理Java应用程序的性能和资源消耗情况。通过JConsole,可以实时查看Java虚拟机(JVM)的运行状态、线程信息、内存使用情况、类加载信息等。要通过JConsole查看线程的状态,你需要先确保你的Java应用程序已经启动,并且JConsole已经附加到该应用程序的进程上。JConsole是Java的一个监控和管理工具,它可以用来监控Java虚拟机(JVM)的性能和资源使用情况,包括线程的状态。
以下是使用JConsole查看线程状态的步骤:
-
启动JConsole:
- 在命令行中输入
jconsole
命令并回车,或者在Java安装目录的bin
文件夹中找到jconsole
可执行文件并运行它。
- 在命令行中输入
-
附加到进程:
- 在JConsole启动后,它会显示一个列表,其中包含当前用户可以访问的所有Java进程。
- 从列表中选择你想要监控的Java应用程序的进程,并点击“Connect”按钮。
-
查看线程状态:
- 连接成功后,JConsole的主界面会显示多个选项卡。点击“Threads”选项卡。
- 在“Threads”选项卡中,你可以看到当前JVM中所有线程的列表,包括每个线程的ID、名称、状态(如Runnable、Waiting、Blocked等)和优先级。
- 你可以通过点击线程列表中的线程来查看更多关于该线程的详细信息,如堆栈跟踪、锁信息等。
-
分析线程状态:
- 使用JConsole提供的线程信息,你可以分析线程的状态和行为,识别可能的性能问题或死锁。
- 如果需要,你可以在JConsole中对线程进行操作,如暂停、恢复或终止线程。
请注意,JConsole只能附加到已经在运行的Java进程上。如果你的应用程序还没有启动,你需要先运行它,然后再使用JConsole来监控。
此外,JConsole的使用可能受到操作系统的安全限制,确保你有足够的权限来附加到目标进程。如果你在使用JConsole时遇到问题,确保你的Java环境和JVM设置是正确的,并且你有足够的权限来执行监控操作。
2.新建状态(New)
线程的新建状态(New State)是线程生命周期中的第一个状态。当一个Thread
对象被实例化后,此时线程对象被创建但还没有调用start()方法时,线程处于新建状态。此时线程对象已经在内存中,但尚未启动。
特点:
- 初始化:在新建状态下,线程对象已经被创建,但还没有分配系统资源,如CPU时间片和内存。
- 未启动:线程尚未被启动,即
start()
方法还未被调用。 - 不可见:在新建状态下,线程对其他线程不可见,它还没有加入到可运行的线程队列中。
- 可配置:在新建状态下,可以在启动线程之前设置线程的属性,如优先级、名称、是否为守护线程等。
状态转换:
- 新建到就绪:当对线程对象调用
start()
方法时,线程会从新建状态转换到就绪状态。在就绪状态下,线程等待操作系统调度器的调度,以便在CPU上执行。 - 新建到终止:如果在新建状态下,线程对象被垃圾收集器回收,那么线程将直接进入终止状态,而不会执行任何操作。
代码实例:
public class NewThreadExample {
public static void main(String[] args) {
// 创建线程对象,此时线程处于新建状态
Thread thread = new Thread(() -> {
System.out.println("Thread is running...");
});
// 线程对象创建后,可以设置其属性,如优先级
thread.setPriority(Thread.MAX_PRIORITY);
//线程启动之前查看状态,结果为(NEW)
System.out.println(thread.getState());
// 启动线程,使其进入就绪状态
thread.start();
//线程启动之后查看状态
System.out.println(thread.getState());
}
}
运行结果如下:
可以看到调用start后状态发生了改变。
3.运行状态(Runnable)
在线程处于RUNNABLE(运行状态)时,称为可工作的,又可以分成正在工作中和即将开始工作。即将开始工作表示这个线程虽然没在CPU执行,但是可以随时调度到CPU执行。开始工作表示线程正在执行任务代码。在这个状态下,线程已经被操作系统的线程调度器分配了CPU时间片,并且正在运行代码。这是线程生命周期中的一个活跃阶段,在这个状态下,线程实际在进行工作。
特点:
- 执行中:线程正在CPU上执行其任务或准备执行。
- 消耗资源:在运行状态下,线程消耗CPU资源。
- 调度:线程的运行由操作系统的调度器管理。调度器根据线程的优先级、调度策略和其他因素决定线程的执行顺序。
- 时间片:线程获得的CPU时间是有限的,通常称为时间片。时间片用完或者操作系统决定执行其他线程时,当前运行的线程可能会被挂起,从而转换到就绪状态。
状态转换:
- 从就绪到运行:当操作系统调度器选择一个就绪状态的线程并分配CPU时间片时,该线程状态变为运行。
- 从运行到就绪:当线程的时间片用完或者线程显式地让出CPU(例如,调用
Thread.yield()
)时,它会回到就绪状态,等待下一次被调度。 - 从运行到阻塞:如果运行中的线程尝试获取一个已经被其他线程持有的锁,或者等待I/O操作完成,它会进入阻塞状态。
- 从运行到终止:当线程的
run()
方法执行完毕或者在run()
方法中抛出一个未捕获的异常时,线程进入终止状态。
我们通过死循环不停止线程,进入jconsole查看一下状态
public class test {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) System.out.println("hello t1");
}, "t1线程");
t1.start();
while (true) {
System.out.println("hello main");
}
}
}
我们主要看t1线程和主线程
这两个线程都已经启动起来了,所以状态自然都是RUNNABLE
4.阻塞状态(Blocked)
线程的阻塞状态(Blocked State)是指线程因为等待获取一个监视器锁(monitor lock)而暂时停止执行的状态。这种状态通常发生在线程尝试进入一个已经被其他线程持有的同步块时。在阻塞状态下,线程无法执行任何操作,直到它获得所需的锁。
特点:
-
等待获取锁:一个常见的情况是线程在同步代码块或同步方法中试图获取一个对象的锁,但该锁已经被其他线程持有。在这种情况下,线程会被阻塞,直到锁被释放。这种阻塞状态被称为同步阻塞。
-
等待I/O操作完成:线程在执行I/O操作时(如读取文件、网络通信等),可能会因为需要等待I/O操作完成而被阻塞。在这种情况下,线程会暂时停止执行,直到I/O操作完成并数据准备好。这种阻塞状态被称为I/O阻塞。
-
等待其他线程通知:线程在调用wait()方法后会进入等待状态,直到其他线程调用notify()或notifyAll()方法来唤醒它。因此,线程可能会因为等待其他线程的通知而被阻塞。这种阻塞状态被称为等待阻塞。
-
等待条件满足:线程在某些情况下需要等待特定条件满足才能继续执行,如果条件不满足,则线程会被阻塞。在这种情况下,线程可能会因为等待条件满足而被阻塞。这种阻塞状态被称为条件阻塞。
-
竞争资源:多个线程之间可能会竞争共享资源,如果资源被其他线程占用或者无法获取到需要的资源,线程可能会被阻塞。在这种情况下,线程会等待资源可用时才能继续执行。
-
解除阻塞:线程在阻塞状态下会等待某些条件满足或者其他线程的操作,一旦条件满足或者被唤醒,线程会从阻塞状态转变为就绪状态,等待操作系统的线程调度器分配CPU时间片,继续执行任务代码。
状态转换:
- 从运行到阻塞:当运行状态的线程尝试获取一个已经被其他线程持有的锁时,它会进入阻塞状态。
- 从阻塞到运行:当持有所需锁的线程释放锁(通过退出同步块或同步方法),阻塞的线程会被唤醒并重新进入就绪状态,等待再次获得CPU时间片以执行。
- 从阻塞到终止:如果阻塞的线程在等待锁的过程中被中断(通过
Thread.interrupt()
方法),它可能会进入终止状态。
代码实例:
public class BlockExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1 acquired lock");
// 模拟长时间操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 acquired lock");
// 模拟操作
}
});
thread1.start(); // 启动thread1,它会首先获取锁
thread2.start(); // 启动thread2,它会尝试获取已经被thread1持有的锁,因此进入阻塞状态
}
}
在这个例子中,thread2
在尝试进入同步块时会因为lock
已经被thread1
持有而进入阻塞状态。只有当thread1
释放锁(在这个例子中是通过Thread.sleep(5000)
完成后)时,thread2
才能获取锁并开始执行。
我们利用死循环进入jconsole查看状态
public class test {
private static final Object resource = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource) {
while(true)
System.out.println("Thread 1: Locked resource");
}
},"t1");
thread1.start(); // 启动thread1,它会首先获取资源锁
Thread thread2 = new Thread(() -> {
synchronized (resource) {
// 这里会等待,直到thread1释放锁
System.out.println("Thread 2: Locked resource");
}
},"t2");
thread2.start(); // 启动thread2,它会尝试获取已被thread1持有的资源锁,因此进入阻塞状态
}
}
该代码t1会先拿到锁,然后进入死循环,t2会一直被阻塞等待t1释放锁。
5.等待状态(Waiting)
在Java中,线程的等待状态(Waiting State)是指线程在等待另一个线程执行特定操作时的状态。这种状态与阻塞状态类似,但有一个关键的区别:在等待状态中,线程等待的不是锁,而是其他线程执行的操作,如通知(notification)或中断(interruption)。等待状态是无限期的,除非另一个线程执行了特定的操作来唤醒等待的线程。
特点:
- 无限期等待:线程在等待状态中会一直等待,直到接收到另一个线程的通知。
- 不消耗CPU:在等待状态中,线程不会消耗CPU资源,因为它不执行任何代码。
- 可被中断:即使在等待状态中,线程也可以被中断。如果线程在等待时收到中断信号,它会抛出
InterruptedException
,并根据线程的中断策略决定下一步的行为。 - 依赖协作:等待状态的线程依赖于其他线程的特定操作来继续执行。
状态转换:
- 从运行到等待:线程调用
Object.wait()
、Thread.join()
或类似的方法,且没有指定等待时间,会从运行状态转换到等待状态。 - 从等待到运行:当另一个线程调用与等待状态相对应的通知方法(如
Object.notify()
或Object.notifyAll()
)时,等待的线程会被唤醒,并尝试重新获取锁,然后返回到运行状态。 - 从等待到阻塞:如果等待的线程在等待时被中断,它可能会转换到阻塞状态,尝试获取锁。
- 从等待到终止:如果等待的线程在等待时被中断,并且线程的中断策略是退出等待状态,那么它将转换到终止状态。
实例代码:
public class WaitingExample {
public static void main(String[] args) {
Thread waitingThread = new Thread(() -> {
synchronized (WaitingExample.class) {
System.out.println("Waiting thread is waiting for notification");
try {
WaitingExample.class.wait(); // 进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Waiting thread has been notified and is running again");
}
});
Thread notifyingThread = new Thread(() -> {
synchronized (WaitingExample.class) {
// 让等待的线程等待一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
WaitingExample.class.notify(); // 唤醒等待的线程
}
});
waitingThread.start(); // 启动等待的线程
notifyingThread.start(); // 启动通知的线程
}
}
在这个例子中,waitingThread
调用了wait()
方法并进入等待状态。notifyingThread
在一段时间后调用了notify()
方法,这将唤醒等待的线程。waitingThread
在接收到通知后,会从等待状态转换回运行状态,并继续执行。
同样改写代码进入jconsole查看
public class test {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
//死等
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
通过join让main线程等待t完成,在等待的期间main就是无休止的等待状态WAITING
6.计时等待状态(Timed Waiting)
在Java中,计时等待状态(Timed Waiting State)是指线程在等待一个确定的时间长度后自动唤醒的状态。这种状态与等待状态(Waiting State)相似,但关键区别在于计时等待有一个超时时间,线程会在超时后自动继续执行,而不需要其他线程的干预。
特点:
- 有限等待:线程在计时等待状态中等待一个特定的时间长度。
- 自动唤醒:一旦超时时间到达,线程会自动从计时等待状态转换到运行状态(Runnable),并尝试重新获取锁。
- 可中断:计时等待状态中的线程可以被中断。如果线程在计时等待期间收到中断信号,它会抛出
InterruptedException
,并根据线程的中断策略决定下一步的行为。 - 超时时间:超时时间是线程进入计时等待状态时设定的,一旦设定,即使线程在等待期间被中断,也会在超时时间到达后自动唤醒。
状态转换:
- 从运行到计时等待:线程调用了带有超时时间参数的方法,如
Thread.sleep(long millis)
、Object.wait(long timeout)
、Thread.join(long millis)
等,会从运行状态转换到计时等待状态。 - 从计时等待到运行:当超时时间到达后,线程会自动转换到运行状态,并等待CPU时间片以继续执行。
- 从计时等待到阻塞:如果线程在计时等待期间被中断,并且线程的中断策略是立即退出等待状态,那么它可能会尝试获取锁并进入阻塞状态。
- 从计时等待到终止:如果线程在计时等待期间被中断,并且线程的中断策略是抛出异常,那么线程可能会抛出
InterruptedException
并终止执行。
代码实例:
public class TimedWaitingExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread is entering timed waiting state");
try {
// 线程进入计时等待状态,等待2000毫秒(2秒)
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread has woken up from timed waiting state");
});
thread.start(); // 启动线程
}
}
改变代码查看jconsole
public class test {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
//有时间限制的等待
t.join(3600 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这段代码和Waiting状态的大体一样,唯一不同的就是join在调用时限制了等待的时间,在超过时间后会被唤醒。此时main的状态就变成了TIMED_WAITING。
7.终止状态(Terminated)
在Java中,终止状态(Terminated State)是线程生命周期的最终状态。当线程的run()
方法执行完毕,或者线程因为某种原因被中断且没有可执行的代码时,线程就会进入终止状态。
特点:
- 执行结束:线程的
run()
方法已经执行完毕,或者线程在执行过程中遇到了未捕获的异常。 - 不可恢复:一旦线程进入终止状态,它就不能再被恢复到运行状态或任何其他状态。
- 资源释放:线程相关的系统资源,如内存、锁等,通常会在线程终止后被释放。
- 垃圾收集:线程对象本身在终止后会成为垃圾收集器的回收目标,最终被清除。
状态转换:
- 从运行到终止:线程的
run()
方法正常执行完毕,或者线程在执行过程中抛出了一个未被捕获的异常,线程会进入终止状态。 - 从阻塞到终止:如果线程在阻塞状态(如等待输入输出操作、等待进入同步块等)时被中断,且中断后没有更多的代码需要执行,线程也会进入终止状态。
- 从等待到终止:如果线程在等待状态(如调用
Object.wait()
)时被中断,且中断后没有更多的代码需要执行,线程同样会进入终止状态。
代码实例:
public class test {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread is running...");
// 模拟线程执行完毕
});
thread.start(); // 启动线程
try {
thread.join(); // 等待线程终止
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程对象,查看状态
System.out.println(thread.getState());
}
}
在这个例子中,当thread
的run()
方法执行完毕后,它将进入终止状态。主线程通过调用thread.join()
等待子线程终止。
运行结果如下: