本文整理了多线程的核心知识,从线程概念、创建方式、生命周期、常用方法,到线程安全与JMM内存模型。

一、多线程基础

①进程(Process):是操作系统分配资源的基本单位,比如打开一个浏览器,就是一个进程。---高隔离性,互不影响


每个进程都有:
·独立的内存空间
·独立的资源(文件、句柄等)

不同进程:

·不能直接访问彼此内存

·需要通过 IPC(进程间通信:如管道,消息队列,Socket... ...)

② 线程(Thread):是程序执行的最小单位,一个进程中可以有多个线程,这些线程共享进程的资源。---高性能,并发,任务间共享数据

线程之间共享:内存(堆)+全局变量+文件资源

但每个线程也有自己的:栈空间+程序计数器

单线程进程:只有一个执行流
多线程进程:多个线程同时执行任务(浏览器进程里可能有:一个线程负责UI,一个线程负责网络请求... ...)

进程vs线程
对比项 进程 线程
定义 资源分配单位 执行单位
内存 独立 共享所属的进程
创建开销
切换开销
通信方式 IPC(复杂) 直接共享(简单但危险)
崩溃影响 不影响其他进程 可能导致整个进程崩溃

Q:为什么需要多线程?

① 提高 CPU 利用率

单线程无法充分利用多核 CPU,多线程可以并发执行。

并发(Concurrency):多任务“轮流执行”,单核 CPU 也可以实现,任务A → 任务B → 任务A → 任务B → …(本质:快速切换)
并行(Parallelism):多任务“同时执行”,需要多核 CPU,核心1:任务A,核心2:任务B(本质:真正同时执行)

② 提高程序效率(并发)
IO等待时可以执行其他任务
减少整体执行时间
③ 线程比进程更轻量
创建更快
切换更快
调度开销更小

二、线程的创建方法

1. 继承 Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
// 启动
new MyThread().start();
2. 实现 Runnable(推荐):避免继承限制,更灵活

使用 Runnable 可以避免 Java 单继承的限制,单继承就是class A extends B, C {} //  不允许

class MyThread extends Thread这个类,已经继承了 Thread,就不能再继承别的类了

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

Thread t = new Thread(new MyRunnable());
t.start();
3. Lambda 写法(常用):可以简化代码

Lambda 是一种“简化匿名函数”的写法,把“方法”当成参数传。

Lambda 的基本语法:(参数) -> { 方法体 }

new Thread(() -> {
    System.out.println("线程执行");
}).start();
4.创建线程池--具体见下一篇博客

提前创建好一批线程等待去用,随用随取,用完放回。

线程池最⼤的好处就是减少每次启动、销毁线程的损耗。

5.实现Callable--具体见后面博客

可解决Runnable无返回结果的问题(Runnable重写的是void run(),void表示没有返回值)

三、Thread 常用方法

start():t.start();   启动线程(真正创建线程)

sleep():Thread.sleep(1000);   让线程休眠1000ms(不会释放锁)

join():  t.join()   所在的主线程等待t线程执行完

interrupt():  t.interrupt()  中断线程(协作机制,不是强制停止)

currentThread():  Thread.currentThread();  获取当前线程

四、线程的生命周期(重点)

线程状态(Thread.State):

  • NEW:新建
  • RUNNABLE:可运行
  • BLOCKED:阻塞(锁竞争)
  • WAITING:等待
  • TIMED_WAITING:超时等待
  • TERMINATED:结束
public class ThreadStateDemo {
    private static final Object lock = new Object();
    public static void main(String[] args) throws Exception {

        Thread t1 = new Thread(() -> {
            try {
                // RUNNABLE
                synchronized (lock) {
                    System.out.println("t1 获取到锁");
                    // TIMED_WAITING(sleep)
                    Thread.sleep(1000);
                    // WAITING(wait)
                    System.out.println("t1 进入等待");
                    lock.wait();
                    // 被唤醒 → RUNNABLE
                    System.out.println("t1 被唤醒,继续执行");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // NEW 状态
        System.out.println("t1 state: " + t1.getState());
        // 启动线程 → RUNNABLE
        t1.start();
        Thread.sleep(100); // 确保 t1 先拿到锁
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2 获取到锁");

                // 唤醒 WAITING 的线程
                lock.notify();

                // TIMED_WAITING(sleep)
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // t2 启动
        t2.start();
        // main 线程等待 t1 执行完 → WAITING(join)
        t1.join();
        // TERMINATED
        System.out.println("t1 执行结束,状态:" + t1.getState());
    }
}

状态转换涉及的方法调用关系

方法 用法 调用者 作用对象 是否释放锁 状态
sleep 让当前线程暂停一段时间,模拟耗时 Thread静态方法 线程类 不释放 TIMED_WAITING
wait 让当前线程释放锁obj并等待其他线程的通知 Object obj 对象锁 释放 WAITING
notify 随机唤醒一个在锁对象obj上等待的线程 Object obj 对象锁 --- 唤醒线程
join 让主线程等待当前线程th执行完毕 Thread th 线程对象 (间接) WAITING
yield 让当前线程主动让出 CPU,让其他线程有机会执行(不保证生效) Thread静态方法 当前线程 不释放 RUNNABLE

五、线程中断机制

[中断标志位 = true]  ≠  [线程已经停止]

它只是一个“请求线程停止”的信号,因为强制杀死线程是不安全的

可能会导致:

  • 数据不一致
  • 锁没释放
  • 资源泄漏

//interrupt(),给线程发中断信号,设置中断标志位为true。线程是否停止取决自己是否处理这个信号
//线程在正常运行--调用 interrupt 后不会立刻停,只是标志位变为 true
//线程正在sleep--如果被 interrupt,会提前结束休眠
//线程在阻塞状态Thread.sleep()、wait()、join()--调用 interrupt 后,会立刻抛异常,中断标志位会被清除(变回 false),是否停止,取决于怎么处理异常
thread.interrupt();

//isInterrupted()获取当前线程的中断状态(true / false),不清除只是“查看”
Thread.currentThread().isInterrupted();

//interrupted()获取当前线程的中断状态,会清除中断标志(把中断状态从 true 改回 false)
Thread.interrupted();

六、线程安全问题(核心重点)

线程安全:多线程执行结果仍然正确
为什么会不安全:

共享数据:多个线程访问同一变量

执行顺序不可控:线程调度是随机的

操作不是原子性 :count++实际上是三步:读取+修改+写回,多线程下可能出错

//共享数据问题(多个线程访问同一变量)
class SharedDataDemo {
    static int count = 0;
    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                count++; // 多线程共享变量
            }
        };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
    }
}
/*
count 被多个线程共享, 两个线程同时修改它
预期:2000,实际:可能小于 2000
因为线程“同时写”,数据被覆盖
*/

//执行顺序不可控(线程调度随机)
public class OrderDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("线程2");
        });
        t1.start();
        t2.start();
    }
}
/*
可能是:线程1线程2    也可能是:线程2线程1
*/

//非原子操作(count++问题)
class AtomicDemo {
    static int count = 0;
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            count++;
        });
        Thread t2 = new Thread(() -> {
            count++;
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + count);
    }
}
/*
多个线程同时读取同一初始值并各自修改后写回,导致后一次写覆盖前一次结果,从而出现数据丢失。
*/

Java 内存模型:主内存 → 线程工作内存 → 修改 → 写回主内存

问题来源:
① 可见性问题:一个线程修改,另一个线程看不到

② 原子性问题:操作被拆分

③ 有序性问题:指令顺序可能改变

指令重排序:编译器 / CPU 会调整代码执行顺序,提高性能

int a = 0;
int b = 0;

a = 1;
b = a;

→实际执行顺序可能变成:

b = a;
a = 1;

Logo

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

更多推荐