在Java开发中,线程与并发是重点知识点。本文将从进程、线程、协程的基础概念入手,逐步拆解线程状态、线程终止、同步机制、线程通信等内容,搭配完整可运行代码示例,帮助大家快速掌握Java并发要点,内容简洁实用,适合新手入门与复习巩固。

一、基础概念:进程、线程、协程

要理解Java线程,首先要区分进程、线程、协程三者的区别,明确各自的定位与作用:

  • 进程:操作系统资源分配的最小单位,拥有独立的内存空间,进程间相互隔离。Java程序启动时,会自动创建一个独立的Java进程。

  • 线程:操作系统CPU调度的最小单位,是进程内的执行单元,共享所属进程的内存与资源。一个进程可以包含多个线程(如Java默认的主线程、GC线程)。

  • 协程/虚拟线程:用户态的轻量级线程,由程序自身调度,无需操作系统内核介入,切换开销极低。Java原生不支持协程,但Java 19+引入的虚拟线程(Virtual Thread),实现了类协程的轻量级并发能力。

对比:进程管资源,线程管调度,协程(虚拟线程)追求高效并发,适合I/O密集型场景。

二、多线程的价值与适用场景

使用多线程的目的是提升程序效率,其价值主要体现在三个方面:

  1. 提高CPU利用率:当线程因I/O操作(如读写文件、网络请求、数据库操作)阻塞时,CPU可切换到其他就绪线程执行,避免CPU空闲等待。

  2. 提升程序响应性:例如GUI程序中,主线程负责界面交互,子线程处理耗时计算,避免界面卡顿。

  3. 简化复杂任务:将多个独立任务拆分为不同线程,代码逻辑更清晰(如多用户Web服务的请求处理)。

补充:单核CPU下,多线程并非毫无意义——I/O密集型场景下,多线程可在等待I/O时切换线程,让CPU持续工作;但纯计算密集型场景下,频繁的上下文切换会增加开销,反而降低效率。

三、Java线程的6种状态及转换

Java线程的生命周期包含6种状态,所有状态均定义在java.lang.Thread.State枚举中,可通过Thread.getState()方法查看线程当前状态,状态及转换规则如下:

  1. NEW(初始态):线程对象已创建,但未调用start()方法,尚未启动。

  2. RUNNABLE(可运行态):调用start()后进入该状态,包含两个子状态——READY(等待CPU分配时间片)和RUNNING(正在执行)。

  3. BLOCKED(阻塞态):线程尝试获取synchronized锁时,因锁被其他线程占用而阻塞。

  4. WAITING(等待态):线程主动放弃CPU,无超时限制,需其他线程显式唤醒(如调用notify())才能恢复。

  5. TIMED_WAITING(超时等待态):带超时限制的等待状态,超时后自动恢复(如调用Thread.sleep(long))。

  6. TERMINATED(终止态):线程执行完run()方法或因未捕获异常退出,生命周期结束,无法再次重启。

总结:NEW → RUNNABLE(start());RUNNABLE ↔ WAITING/TIMED_WAITING(等待/唤醒);RUNNABLE ↔ BLOCKED(竞争锁);RUNNABLE → TERMINATED(执行完毕/异常)。

四、线程优先级验证(完整代码示例)

Java线程支持设置优先级(1~10,默认5),但优先级的调度效果依赖操作系统,无法保证严格生效。以下是验证线程优先级对执行次数影响的完整代码:

package basic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Priority {
    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs = new ArrayList<>();
        // 前5个线程设为最低优先级,后5个设为最高优先级
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread t = new Thread(job, "thread" + i);
            t.setPriority(priority);
            t.start();
        }

        notStart = false; // 启动所有线程
        TimeUnit.SECONDS.sleep(10); // 让线程执行10秒
        notEnd = false; // 终止所有线程

        // 输出各线程执行次数,观察优先级影响
        for (Job job : jobs) {
            System.out.println("Job priority" + job.priority + " Job Count" + job.jobCount);
        }
    }

    // 线程执行的任务
    static class Job implements Runnable {
        private int priority;
        private long jobCount;

        public Job(int priority) {
            this.priority = priority;
        }

        @Override
        public void run() {
            // 等待notStart变为false,统一启动
            while (notStart) {
                Thread.yield(); // 主动让出CPU时间片
            }
            // 持续执行,直到notEnd变为false
            while (notEnd) {
                Thread.yield();
                jobCount++;
            }
        }
    }
}

说明:Thread.yield()方法会让当前线程主动让出CPU,从RUNNING回到READY状态,重新参与调度,但不改变线程的整体状态(仍属于RUNNABLE)。

五、线程终止:优雅终止的两种方式(完整代码示例)

Java中禁止使用stop()suspend()resume()等废弃方法(会导致数据不一致、死锁等问题),推荐使用「volatile标志位」和「interrupt()中断」两种优雅终止方式,以下是完整代码:

package basic;

import java.util.concurrent.TimeUnit;

public class Shutdown {
    public static void main(String[] args) throws InterruptedException {
        // 方式1:使用 interrupt() 终止线程(可中断阻塞状态)
        Runner one = new Runner();
        Thread thread = new Thread(one, "CountThread");
        thread.start();
        TimeUnit.SECONDS.sleep(1); // 让线程执行1秒
        thread.interrupt(); // 发送中断信号

        // 方式2:使用 volatile 标志位终止线程(实现简单)
        Runner two = new Runner();
        thread = new Thread(two, "CountThread");
        thread.start();
        TimeUnit.SECONDS.sleep(1); // 让线程执行1秒
        two.cancel(); // 修改标志位,终止线程
    }

    private static class Runner implements Runnable {
        private long i = 0;
        private volatile boolean on = true; // 线程终止标志位(volatile保证可见性)

        @Override
        public void run() {
            // 双重判断:标志位 + 中断状态,确保线程安全终止
            while (on && !Thread.currentThread().isInterrupted()) {
                i++; // 模拟业务逻辑
            }
            System.out.println("count:" + i); // 输出执行次数
        }

        // 取消线程(修改标志位)
        public void cancel() {
            on = false;
        }
    }
}

说明:

  • volatile标志位:通过on变量控制线程循环,实现简单,但线程处于阻塞状态(如sleep、wait)时,无法立即响应。

  • interrupt()中断:向线程发送中断信号,线程通过isInterrupted()检测信号,可中断阻塞状态(抛出InterruptedException),适配范围更广。

  • 两种方式均为「协商式终止」,让线程自行退出,保证资源安全释放。

六、守护线程(Daemon Thread)要点

守护线程是为用户线程服务的“后台线程”,规则与特点如下:

比喻:用户线程 = 前线作战士兵(业务),守护线程 = 后勤兵(后台支持)。

  • 关键规则:当所有用户线程执行完毕,JVM会直接退出,不等待守护线程执行完毕。

  • 注意事项:setDaemon(true)必须在start()之前调用,否则抛出异常;守护线程的finally块不保证执行,不适合处理资源释放(如关闭文件、释放连接)。

  • 典型场景示例:JVM的GC线程、后台日志刷盘线程、心跳检测线程。

七、同步机制:volatile与synchronized

多线程操作共享资源时,会出现线程安全问题(如数据错乱),Java提供了两种同步方式,二者对比与使用场景如下:

7.1 volatile(轻量级同步关键字)

作用:保证变量的可见性(强制线程直接读写主内存,不使用本地缓存);禁止指令重排序不保证原子性

适用场景:状态标记量(如线程终止的标志位)、双重检查锁等。

7.2 synchronized(重量级同步关键字)

作用:保证原子性(同一时间仅一个线程执行临界区代码)、可见性有序性,是实现线程安全最基础、最常用的方式。

使用方式:可修饰普通方法(锁当前对象)、静态方法(锁类对象)、代码块(锁指定对象)。

底层原理:基于对象的Monitor(监视器锁)实现,通过monitorenter(加锁)和monitorexit(释放锁)指令保证互斥性。

7.3 对比

特性

volatile

synchronized

原子性

不保证

保证

可见性

保证

保证

有序性

禁止指令重排

间接保证

阻塞性

非阻塞

阻塞

八、线程通信:wait()/notify()/notifyAll()(白雪公主与王子示例)

多线程协作时,需通过通信实现“等待-唤醒”逻辑,wait()notify()notifyAll()是Java原生的线程通信方式,以下用“白雪公主与王子”的示例,演示完整使用场景:

public class Shumeiren {
    // 全局标志位,控制白雪公主线程是否继续等待(解药是否送达)
    static boolean flag = true;

    public static void main(String[] args) {
        // 启动白雪公主线程(等待解药)
        new Thread(new Baixuegongzhu(), "白雪公主").start();
        // 启动王子线程(发送解药)
        new Thread(new Wangzi(), "王子").start();
    }

    // 白雪公主线程(等待方)
    static class Baixuegongzhu implements Runnable {
        @Override
        public void run() {
            // 锁对象必须与王子线程一致(此处用Shumeiren.class全局锁)
            synchronized (Shumeiren.class) {
                // 用while循环判断条件,防止虚假唤醒
                while (Shumeiren.flag) {
                    try {
                        System.out.println("白雪公主:进入睡眠状态,等待解药...");
                        // 调用wait(),主动释放锁,进入等待队列
                        Shumeiren.class.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("白雪公主:被唤醒,拿到解药啦!");
            }
            System.out.println("白雪公主:有了解药,执行完毕");
        }
    }

    // 王子线程(通知方)
    static class Wangzi implements Runnable {
        @Override
        public void run() {
            // 同一把锁,保证线程通信安全
            synchronized (Shumeiren.class) {
                System.out.println("王子:给白雪公主发送解药!");
                // 唤醒等待队列中的一个线程(此处唤醒白雪公主)
                Shumeiren.class.notify();
                // 修改标志位,让白雪公主线程退出循环
                Shumeiren.flag = false;

                try {
                    // 模拟王子后续操作,持有锁5秒(验证wait()释放锁)
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("王子:任务执行完毕");
            }
        }
    }
}

使用规则:

  1. 必须在synchronized同步块/方法中调用,否则抛出IllegalMonitorStateException

  2. 锁对象必须一致:wait()notify()/notifyAll()必须使用同一个锁对象。

  3. 必须用while循环判断条件,防止“虚假唤醒”(线程意外被唤醒后,再次校验条件)。

  4. wait()调用后会主动释放锁,而sleep()不会释放锁。

  5. 优先使用notifyAll():唤醒所有等待线程,避免信号丢失,比notify()更安全。

九、总结

  • 进程管资源,线程管调度,协程(虚拟线程)追求轻量高效,适合高并发I/O场景。

  • 多线程的核心价值是提升CPU利用率、处理阻塞,单核CPU仅I/O密集型场景有优势。

  • 线程终止需用volatile标志位或interrupt(),禁止使用stop()等废弃方法。

  • volatile保证可见性与有序性,synchronized保证原子性、可见性、有序性,底层依赖Monitor机制。

  • wait()/notify()/notifyAll()是线程通信的方式,必须配合synchronized与while循环使用。

所有代码均已测试可运行,可直接复制到IDE中调试学习。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐