Java多线程与JConsole实践:从线程状态到性能优化!!!

发布于:2025-03-27 ⋅ 阅读:(28) ⋅ 点赞:(0)

一、前言

在操作系统和并发编程中,线程作为最小的执行单位,其生命周期中会经历多个不同的状态。理解这些状态及其转换非常重要,因为它们直接关系到程序的正确性和性能。本文将详细解析线程的各个状态及其转换关系。

二、JConsole 使用教程

JConsole 是一种 Java 监控和管理控制台工具,可以用于监视 Java 虚拟机(JVM)的性能和资源利用情况。它提供了一种图形化界面,可以实时查看 JVM 的运行状态、内存使用情况、线程活动、垃圾回收等信息,以及执行一些管理操作

1.启动JConsole

JConsole 是包含在 JDK 中的一个工具,因此首先要确保已经安装了 JDK。然后,找到jconsole.exe的位置然后 双击 jconsole.exe 启动 JConsole。
在这里插入图片描述

2.连接到 Java 进程

启动 JConsole 后,会弹出一个界面,显示所有正在运行的 Java 进程。选择要监控的 Java 进程,并点击连接按钮

我们先简单看一个代码:

public class Tsleep {
    public static void main(String[] args)  {
        Thread t=new Thread(()->{
            try {
                Thread.sleep(3000);
                System.out.println("运行线程...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1");
        t.start();
        System.out.println("main....线程运行...");
    }
}

启动项目:然后打开jconsoel选中我们需要观察的线程。
在这里插入图片描述

3.本地连接

在本地进程中会展示出当前计算机所有正在运行的 Java 程序,只需选中双击进入,再点击“不安全的连接”即可进入到监听界面

在这里插入图片描述

在这里插入图片描述
然后我们就可以通过这个jconsole来更好的观察线程状态了。


二、线程的基本状态

2.1新建状态(New)

  • 当线程被创建时,线程处于新建状态。
  • 此时线程尚未开始执行,只是被操作系统或运行时环境识别为一个线程对象。
  • 例如:使用 new Thread() 创建一个线程实例,线程处于新建状态。

NEW: 安排了工作, 还未开始行动。

public class Tsleep {
    public static void main(String[] args)  {
        Thread t=new Thread(()->{
            try {
                Thread.sleep(10000);
                System.out.println("运行线程...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1");
        System.out.println(t.getState());
        t.start();
    }
}

在这里插入图片描述

线程在start之前的状态。
可以看到NEW状态被打印出来了,这就是新建状态。

2.2就绪状态(Ready)

  • 线程被操作系统的调度器识别为可执行状态,等待 CPU 资源。
  • 此时线程已经被初始化并且具备了运行条件。
    当线程被创建(如 new Thread())后,线程处于 Ready 状态,但尚未开始执行。

例如:

Thread thread = new Thread(() -> {
    System.out.println("Hello, Thread!");
});
// 此时线程处于 Ready 状态,但尚未调用 start()

2.3运行状态(Running)

线程获得 CPU 时间片,开始执行线程的 run() 方法。
这是线程执行任务的核心状态。

RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作

public class Dom12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
           while (true){
              //这里什么都不做
           }
        });
        t.start();
        //真正工作的状态
        System.out.println(t.getState());
    }
}

在这里插入图片描述

Ready 状态与 Running 状态的区别

特性 Ready 状态(就绪状态) Running 状态(运行状态)
CPU 资源 等待 CPU 资源 获得 CPU 资源,正在执行任务
执行状态 未执行 执行中
进入方式 调用 start()、从阻塞/等待恢复 调度器选择并分配 CPU 时间片
退出方式 获得 CPU 后进入 Running 状态 时间片用完或被中断,返回 Ready 状态

2.4 阻塞状态(Blocked)

  • 线程因等待某些资源(如 I/O、锁等)而暂停执行。
  • 阻塞状态的线程不在就绪队列中,直到等待的资源可用才会解除阻塞。
  • 例如:线程尝试获取一个已经被其他线程占用的锁时,会进入阻塞状态。
package tset1;

public class Tsleep {
    public static void main(String[] args) throws InterruptedException {
        // 共享的锁对象
        Object lock = new Object();
// 线程 1
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread1: 已进入同步块");
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Thread1: 迭代 " + i);
                        Thread.sleep(3000); // 睡眠3000毫秒
                    }
                } catch (InterruptedException e) {
                    System.out.println("Thread1: 被中断");
                }
                System.out.println("Thread1: 已退出同步块");
            }
        },"T1");

// 线程 2
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread2: 已进入同步块");
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("Thread2: 迭代 " + i);
                        Thread.sleep(3000); // 睡眠 3000 毫秒
                    }
                } catch (InterruptedException e) {
                    System.out.println("Thread2: 被中断");
                }
                System.out.println("Thread2: 已退出同步块");
            }
        },"T2");

// 启动两个线程
        thread1.start();
        thread2.start();

// 主线程等待两个子线程完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            System.out.println("Main: 在等待子线程完成时被中断");
        }
    }
}

代码解释

共享锁对象:

Object lock = new Object(); 定义了一个共享的锁对象,用于在多个线程之间同步。

线程创建:

创建了两个线程 thread1 和 thread2,它们都将尝试获取锁 lock。
在 synchronized (lock) 块内,线程将执行一个循环,睡眠500毫秒,模拟执行任务。

启动线程:

thread1.start(); 和 thread2.start(); 启动了两个线程,让它们竞争锁 lock。
主线程等待:
thread1.join();和thread2.join(); 确保主线程在等待两个子线程完成后再退出。

在这里插入图片描述

2.5. 等待状态(Waiting)

WAITING 状态是无限等待状态,直到被其他线程唤醒。

public class Dom14 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (true){
                //System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new NullPointerException();
                }
            }
        });
        t.start();
        //join() 会出现WAITING状态  --死等
        t.join();
        System.out.println(t.getState());
    }
}

通过控制台观察不到,因为出现了死等所以,所以jconsole进行观察:
在这里插入图片描述

阻塞状态与等待状态的区别

在这里插入图片描述


2.6 等待状态(TIMED_WAITING)

TIMED_WAITING 状态是有限等待状态,线程会在指定时间后自动退出。

public class Dom13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (true){
                //System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new NullPointerException();
                }
            }
        });
        t.start();
        Thread.sleep(100);
        //指定时间的阻塞 TIMED_WAITING 状态
        System.out.println(t.getState());
        //join(时间)也会进入TIMED_WAITING 状态
       t.join(100);
        System.out.println(t.getState());
    }
}

在这里插入图片描述

2.7终止状态(TERMINATED)

  • 线程完成执行或 debido a una interrupción external، 执行结束。
  • 一旦线程终止,其资源会被操作系统回收。

TERMINATED: 工作完成了.

public class Dom11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            System.out.println("hello thread");
        });
        t.start();
        Thread.sleep(1000);
        //内核当中的线程已经结束了(工作结束了)
        System.out.println(t.getState());
    }
}

在这里插入图片描述


二、线程状态的转换

线程状态之间的转换是由操作系统的调度器和线程自身的行为决定的。以下是线程状态转换的常见情况:

在这里插入图片描述
⼤家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。在这里插入图片描述
例子:(银行的例子)

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;
当李四、王五开始去窗口排队,等待服务,就进⼊到 RUNNABLE 状态。该状态并不表示已经被银行工作⼈员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
当李四、王五因为⼀些事情需要去忙,例如需要填写信息、回家取证件、发呆⼀会等等时,进入BLOCKED 、 WATING 、 TIMED_WAITING 状态;如果李四、王五已经忙完,为 TERMINATED 状态。


  1. 就绪 → 运行
  • 操作系统的调度器选择一个就绪状态的线程,并分配 CPU 时间片。
  • 例如
    Thread thread = new Thread();
    thread.start();   
    
  1. 运行 → 就绪

    • 线程的 CPU 时间片用完,被调度器剥夺 CPU 使用权。

    - 例如:操作系统使用时间片调度,线程的时间片到期后会被暂停。

  2. 运行 → 阻塞

  • 线程在执行过程中等待某些资源(如 I/O 或锁),主动放弃 CPU。

例如:线程尝试获取一个被其他线程占用的锁:
java synchronized (lock) { //如果 lock 已被另一个线程占用,此线程将进入阻塞状态 }

  1. 运行 → 等待
  • 线程主动调用某些方法(如 sleep()),等待特定条件满足。

** 例如:**
java Thread.sleep(1000); //线程进入等待状态,等待 1 秒

  1. 阻塞/等待 → 就绪
  • 阻塞状态的线程等到所需资源可用(如锁被释放)。
  • 等待状态的线程等到超时或被其他线程唤醒。
  • 例如:
    // 阻塞状态的线程获取锁后会自动转移到就绪状态
    

三、线程安全与状态管理

在多线程编程中,线程状态的管理直接关系到程序的正确性和性能。以下是需要注意的事项:

  1. 避免长时间占用 CPU
  • 长时间占用 CPU 的线程会导致其他线程饥饿。
  • 建议使用 yield()sleep() 方法让出 CPU。
  1. 合理加锁和解锁
  • 锁的不当使用可能导致线程长时间阻塞。
  • 例如:在持有锁时避免执行耗时操作。
  1. 正确使用等待/通知机制
  • 使用 wait()notify() 方法可以更高效地管理线程间的协作。
  • 例如:实现生产者-消费者模式时,使用等待队列管理线程状态。

四、总结

在本文中,我们将通过JConsole这款强大的工具,深入探索Java多线程的核心知识。从线程的基本状态(如新建、就绪、运行、阻塞、等待和终止)到线程状态之间的转换机制,帮助开发者更好地理解和管理线程。通过实践性的教程,读者将学会如何利用JConsole监控和调试线程,从而优化应用程序的性能和稳定性。这篇文章适合Java开发人员和对并发编程感兴趣的学习者,旨在提供一份清晰易懂的指南,助力在多线程编程中游刃有余

如果您需要更深入的内容或具体案例,可以在留言区告诉我!