摘要:

本文从最基础的线程概念入手,系统地梳理了 Java 中线程的定义、创建方式、状态流转以及线程安全问题。结合 JMM(Java 内存模型)的原理,深入讲解了多线程中的可见性、原子性、有序性问题,并补充了 waitnotify 的底层逻辑与配合方式。看完这篇,你会对多线程体系形成清晰的整体认知。


一、什么是线程?

线程(Thread)是轻量级的进程
每个线程都有自己独立的任务,但多个线程共享进程的系统资源。

二、进程与线程的区别

可以把进程比作一座工厂,而线程就是工厂里的生产线

一个工厂(进程)里可以有多条生产线(线程),每条生产线都有自己的工作任务,比如一条做组装,一条做包装。
这些生产线虽然各自独立完成不同任务,但它们共享同一工厂的资源——使用同样的厂房、电力、设备和原材料。

因此,线程就像生产线,是进程内部的轻量级执行单位,负责在共享资源的基础上并行完成不同的工作。


二、进程与线程的区别

对比点 进程 线程
含义 正在运行的程序 进程中的执行单元
系统分配资源单位 ✅ 最小单位 ❌ 共享进程资源
CPU 调度单位 ✅ 最小单位
影响范围 相互独立 线程之间会相互影响

一个进程中至少有一个线程(即主线程)。
当线程出问题时,可能会导致整个进程崩溃;但进程之间互不影响。


三、线程的创建方式

  1. 继承 Thread 类,重写 run() 方法。

    class MyThread extends Thread {
        @Override
        public void run() {
            // 线程要执行的代码
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程正在运行:" + i);
            }
        }
    }
    
    public class Demo1 {
        public static void main(String[] args) {
            MyThread t = new MyThread(); // 创建线程对象
            t.start();                   // 启动线程(自动调用 run())
            System.out.println("主线程结束");
        }
    }
    

    重点描述“线程”的定义与行为本身。

  2. 实现 Runnable 接口,重写 run() 方法。

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("Runnable 线程运行中:" + i);
            }
        }
    }
    
    public class Demo2 {
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable();  // 创建任务对象
            Thread t = new Thread(mr);         // 将任务交给线程执行
            t.start();
            System.out.println("主线程继续执行");
        }
    }
    

    重点描述“任务逻辑”,推荐这种写法,更灵活。

  3. 匿名内部类方式
    直接在创建线程时定义任务逻辑,更简洁。

    public class Demo3 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("匿名内部类线程运行中:" + i);
                    }
                }
            });
            t.start();
            System.out.println("主线程执行完毕");
        }
    }
    

     

  4. Lambda 表达式方式
    由于 Runnable 是函数式接口,可以使用 Lambda 表达式快速创建线程。

    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Lambda 线程运行中:" + i);
                }
            });
            t.start();
            System.out.println("主线程继续执行");
        }
    }
    

四、为什么要使用多线程?

主要目的:提升程序运行效率

  • 当任务重、逻辑复杂时,多线程能有效并行执行,提升 CPU 利用率;

  • 当任务轻、逻辑简单时,多线程反而会引入上下文切换开销,降低效率。


五、线程的六种状态

Java 在系统的 PCB(进程控制块)基础上做了封装,将线程状态划分为以下几种:

状态 含义
NEW 线程对象创建,但未启动(未调用 start())
RUNNABLE 包含“运行中 + 就绪”状态,随时可能被调度执行
BLOCKED 等待锁资源(synchronized)时的状态
WAITING 无期限等待,如 wait()join()
TIMED_WAITING 带超时等待,如 wait(time)sleep(time)join(time)
TERMINATED 执行结束,系统资源释放

六、什么是线程安全?

当多个线程同时访问共享资源时,程序运行结果出现非预期情况,就发生了线程安全问题。


七、导致线程安全的三大原因

  1. 线程是抢占式执行的,不按顺序来;

  2. 多线程修改了同一个共享变量

  3. 没有正确地保证:

    • 原子性(一个操作要么全做,要么不做);

    • 可见性(一个线程的修改能否被其他线程看到);

    • 有序性(代码执行顺序是否和预期一致)。


八、JMM —— Java 内存模型

Java 内存模型规定:

每个线程都有自己的工作内存(相当于 CPU 的寄存器缓存)。
线程修改变量时,不能直接改主内存中的值,而是先复制到自己的工作内存中修改,之后再刷新回主内存。

JMM 要保证的三大特性:

  1. 原子性:操作不可被中断;

  2. 可见性:线程间修改能被及时看到;

  3. 有序性:防止 CPU 或编译器的指令重排。


九、解决线程安全的方式

1. synchronized

  • 可以修饰方法或代码块;

  • 解决原子性问题

  • 通过原子性间接保证了可见性

  • 但无法解决指令有序性问题。

2. volatile

  • 修饰共享变量;

  • 通过内存屏障保证可见性;

  • 禁止指令重排序,保证有序性

  • 不保证原子性


十、wait、notify、notifyAll 的用法

  • 这些方法都定义在 Object 类 中;

  • 必须和 synchronized 一起使用;

  • 调用 wait() 的线程会:

    • 释放锁;

    • 进入等待队列;

    • notify()notifyAll() 唤醒后重新竞争锁;

    • 拿到锁后从 wait() 处继续执行。

方法 作用
wait() 当前线程进入等待状态(释放锁)
notify() 随机唤醒一个正在等待的线程
notifyAll() 唤醒所有等待的线程

⚠️ 唤醒和等待的线程必须针对同一个锁对象,否则无法通信。

 

 

 

 

Logo

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

更多推荐