1. 线程的关键属性

1.1 Thread的常见构造方法

1 Thread t1 = new Thread();
2 Thread t2 = new Thread(new MyRunnable());
3 Thread t3 = new Thread("这是我的名字");
4 Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

1.2 Thread 的几个常见属性

我们先来看一下几个常用的方法,稍后会在代码中使用到~

  • ID是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具中会遇到可以自己起名也可以自动起名
  • 状态标识线程当前所处的一个情况,下面会讲到
  • 优先级高的线程在理论上来说更容易被调度到(线程是随机调度)
  • 关于后台线程,只需要记住一点:JVM在一个线程的所有非后台线程结束之后,才会结束运行
  • 是否存活最简单的理解就是run方法是否运行结束
  • 线程的中断问题在下面会逐步详解

这里对于部分属性选择在线程可视化界面中进行观察~

1.2.1 ID

首先我们需要找到jconsole,在前言中有简介

然后我们在IDEA中去执行一个多线程的代码,以下图为例:

运行起来之后进入jconsole程序(进入自己创建的线程)

选中后直接点连接

进入后选择不安全的连接

进入后点击上方的线程选项

点击线程之后如下图:

以上就是线程的ID了~~

代码:

public class demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(3000);
        }
    }
}

1.2.2 名称

在上面ID的简介中,我们提到了一个子线程的默认名称:Thread-?(0开始,0,1,2.......)

我们在使用lambda表达式的时候可以给线程起一个名字:

public class demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"我的子线程");

        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(3000);
        }
    }
}

现在起好名称后我们运行程序,然后重启一下jconsole再去线程中看看名称是Thread-0还是我的子线程

显而易见,我们的名称修改成了~

1.2.3 状态

Java中线程一般有以下状态:

  1. NEW(新建)
    线程被创建,但尚未调用 start() 方法。

  2. RUNNABLE(可运行)
    线程正在运行或准备运行(等待CPU时间片)。
    注意:在操作系统层面,此状态可能对应 就绪(Ready) 和 运行(Running) 两种状态。

  3. BLOCKED(阻塞)
    线程因等待监视器锁(如synchronized)而阻塞,只有进入synchronized代码块时才会进入此状态

  4. WAITING(等待)
    线程因调用 wait()join()LockSupport.park() 等方法进入无限期等待,需要其他线程显式唤醒。

  5. TIMED_WAITING(计时等待)
    线程进入有限时间的等待(如 sleep(ms)wait(timeout)join(timeout))。

  6. TERMINATED(终止)
    线程执行完毕或异常退出。

本篇先只讲前两个

1.2.3.1 NEW(新建)

这个状态就是线程被我们创建了,但是并没有调用start方法让线程跑起来~

来看个代码理解一下~

public class demo2 {
    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 RuntimeException(e);
               }
           }
        }"yaya");

        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

可以看到我们有两个线程,一个是yaya,一个是主线程main,正常情况下我们应该是需要两个线程一起跑的,但是这里我们针对yaya没有调用start方法让它跑起来,也就是说yaya无法执行!!

这里我们把代码做一些改动,再去观察一下~:

public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
           for(int i = 0; i < 3; i++){
               System.out.println("hello thread");
               try{
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        },"yaya");

        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

这里我们换成了for循环,让yaya执行三次,main执行三次,但是我们没有让yaya跑起来,我们应该看到的是main执行三次

可以看到,结局是意料之中的~

与此同时,我们在线程监视器中也看不到yaya的影子~

在我们以后写多线程相关的代码时,我们需要注意这种情况,不过也可以利用这个特性去完成一些特殊的工作~

加餐

相信看到这里很多人会想:为什么会出现我们明明已经创建了yaya这个线程,而在线程监视器上却看不到yaya这样的情况呢?这里来做一个解答:

线程的创建过程是分阶段的:

Thread t = new Thread(() -> System.out.println("Hello"));  // 此时只是Java对象
// t.start();  // 没有调用start(),操作系统层面没有创建真正的执行实体
  • new Thread()只是在JVM的堆内存中创建了一个Java对象
  • start()才会调用操作系统API创建真正的系统线程!
Java层: Thread对象(NEW状态)
        ↓ start()调用
操作系统层: 创建内核线程/轻量级进程(分配TCB、栈空间等)
        ↓ 进入就绪队列
监视器可见

比如我们来一个案例更直观:在这里面我们创建1000个线程,但是start10个线程,然后打印所有线程

import java.util.ArrayList;
import java.util.List;

public class demo2 {
    public static void main(String[] args) throws Exception {
        // 创建1000个线程但不启动
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            threads.add(new Thread(() -> {
                try {
                    Thread.sleep(60000); // 睡1分钟
                } catch (Exception e) {}
            }, "Thread-" + i));
        }

        System.out.println("创建了 " + threads.size() + " 个线程");
        System.out.println("按回车启动前10个线程...");
        System.in.read();

        // 只启动前10个线程
        System.out.println("启动前10个线程...");
        for (int i = 0; i < 10; i++) {
            threads.get(i).start();
        }

        // 简单打印所有线程状态
        System.out.println("\n=== 线程状态总览 ===");
        int started = 0;
        int notStarted = 0;

        for (int i = 0; i < threads.size(); i++) {
            Thread t = threads.get(i);
            if (t.isAlive()) {
                started++;
                if (started <= 10) { // 只显示前10个启动的线程详情
                    System.out.println(t.getName() + " | 状态: " + t.getState() + " | 存活");
                }
            } else {
                notStarted++;
            }
        }

        System.out.println("\n=== 统计 ===");
        System.out.println("已启动线程数: " + started);
        System.out.println("未启动线程数: " + notStarted);
        System.out.println("总线程数: " + threads.size());

        System.out.println("\n现在可以运行 jstack 查看线程状态");
        System.out.println("主线程将等待60秒...");
        Thread.sleep(60000);
    }
}

可以看到我们只start10个线程,打印出来的线程只有10个,剩下的990个都是未启动的

于是得出结论:

  • 创建线程对象≠启动线程
  • 线程在调用start()之前只是普通的对象
  • 在JVM工具中也是只有调用start()后,线程才会被JVM接管

JVM工具获取的是活动线程,而不会获取到new状态的线程!!

1.2.3.2 RUNNABLE(可运行)

Runnable分为两种情况:1.真正的在CPU上在运行。 2.就绪状态,等待CPU执行。这里给这两种情况分别举例

第一种情况:在执行:

public class RealRunning {
    public static void main(String[] args) {
        // 这个线程会真正占用CPU
        Thread t = new Thread(() -> {
            System.out.println("开始计算");
            long sum = 0;
            for (long i = 0; i < 10000000; i++) {
                sum += i;
            }
            System.out.println("计算完成");
        });
        t.start();

        // 主线程监控
        while (t.isAlive()) {//isAlive是检测线程是否存活,虽在后面讲,但此处要用到
            System.out.println("线程状态: " + t.getState());  // 一直显示RUNNABLE
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

在计算过程中,线程的状态是Runnable。这里的Runnable是正在执行的状态

第二种:就绪状态:

public class ss {
    public static void main(String[] args) throws Exception {
        System.out.println("CPU核心数: " + Runtime.getRuntime().availableProcessors());

        // 创建大量一直计算的线程(不sleep)
        int threadCount = 20;  // 远超CPU核心数

        for (int i = 1; i <= threadCount; i++) {
            final int id = i;
            new Thread(() -> {
                System.out.println("线程" + id + "启动,将一直计算");

                // 一直计算,不sleep,不让出CPU
                long counter = 0;
                while (true) {
                    counter++;
                    if (counter % 100000000 == 0) {
                        System.out.println("线程" + id + "仍在计算... 状态: RUNNABLE");
                    }
                }
            }, "Compute-" + i).start();

            Thread.sleep(50);  // 错开启动时间
        }

        // 监控
        Thread.sleep(1000);
        System.out.println("\n=== 当前状态 ===");
        System.out.println("20个线程都在RUNNABLE状态");
        System.out.println("但只有" + Runtime.getRuntime().availableProcessors() +
                "个能真正运行");
        System.out.println("其他在操作系统就绪队列等待CPU");
    }
}

这个就是一个典型的例子了~不过不建议运行哦~风扇会疯狂转,如果好奇可以试试(及时停止)~

不说废话了,现在开始讲原理:


CPU核心数: 4(假设是4核电脑)
线程1启动,将一直计算
线程2启动,将一直计算
...
线程20启动,将一直计算

===状态===
20个线程都在RUNNABLE状态
只有4个能真正运行
其他在操作系统就绪队列等待CPU

Java层面(你看到的):

  • 所有20个线程:Thread.getState() 都返回 RUNNABLE

  • 看起来:20个线程都在"运行"

操作系统层面(实际发生的):

CPU核心0:正在执行 线程3
CPU核心1:正在执行 线程8  
CPU核心2:正在执行 线程15
CPU核心3:正在执行 线程20
就绪队列:[线程1, 线程2, 线程4, 线程5, 线程6, 线程7, 线程9, 线程10, 线程11, 线程12, 线程13, 线程14, 线程16, 线程17, 线程18, 线程19]
  • 只有4个线程在真正占用CPU

  • 其他16个都就绪队列排队

为什么都显示RUNNABLE?

Java的线程状态是逻辑状态

  • RUNNABLE = "我可以运行"

  • 不区分:"正在运行" vs "就绪等待"

这就好比:

  • 电影院检票口:20个人都拿着票(RUNNABLE)

  • 但只有4个检票口(CPU核心)

  • 16个人在排队(就绪队列),但他们都有票(显示RUNNABLE)

就绪状态的本质就是——有资格运行,但是还没轮到

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐