【Java并发基础0】多线程核心知识详解(线程及创建、生命周期、线程中断机制,线程安全问题)
本文整理了多线程的核心知识,从线程概念、创建方式、生命周期、常用方法,到线程安全与JMM内存模型。
一、多线程基础
①进程(Process):是操作系统分配资源的基本单位,比如打开一个浏览器,就是一个进程。---高隔离性,互不影响
每个进程都有:
·独立的内存空间
·独立的资源(文件、句柄等)不同进程:
·不能直接访问彼此内存
·需要通过 IPC(进程间通信:如管道,消息队列,Socket... ...)
② 线程(Thread):是程序执行的最小单位,一个进程中可以有多个线程,这些线程共享进程的资源。---高性能,并发,任务间共享数据
线程之间共享:内存(堆)+全局变量+文件资源
但每个线程也有自己的:栈空间+程序计数器
单线程进程:只有一个执行流
多线程进程:多个线程同时执行任务(浏览器进程里可能有:一个线程负责UI,一个线程负责网络请求... ...)
| 对比项 | 进程 | 线程 |
| 定义 | 资源分配单位 | 执行单位 |
| 内存 | 独立 | 共享所属的进程 |
| 创建开销 | 大 | 小 |
| 切换开销 | 大 | 小 |
| 通信方式 | 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;
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐






所有评论(0)