目录
- [⚡ 26 多线程基础------Thread、Runnable与线程安全](#⚡ 26 多线程基础——Thread、Runnable与线程安全)
-
- 一、线程概述
-
- [1.1 进程与线程](#1.1 进程与线程)
- [1.2 为什么使用多线程](#1.2 为什么使用多线程)
- 二、创建线程的方式
-
- [2.1 方式一:继承Thread类](#2.1 方式一:继承Thread类)
- [2.2 方式二:实现Runnable接口(推荐)](#2.2 方式二:实现Runnable接口(推荐))
- [2.3 方式三:实现Callable接口(有返回值)](#2.3 方式三:实现Callable接口(有返回值))
- [2.4 三种方式对比](#2.4 三种方式对比)
- 三、线程的生命周期
-
- [3.1 线程状态](#3.1 线程状态)
- [3.2 线程状态查询](#3.2 线程状态查询)
- 四、Thread类常用方法
-
- [4.1 常用方法一览](#4.1 常用方法一览)
- [4.2 线程命名与获取](#4.2 线程命名与获取)
- [4.3 线程休眠(sleep)](#4.3 线程休眠(sleep))
- [4.4 线程加入(join)](#4.4 线程加入(join))
- [4.5 守护线程(Daemon)](#4.5 守护线程(Daemon))
- [4.6 线程中断](#4.6 线程中断)
- 五、线程安全问题
-
- [5.1 什么是线程安全问题](#5.1 什么是线程安全问题)
- [5.2 线程安全问题的原因](#5.2 线程安全问题的原因)
- [5.3 线程安全问题的条件](#5.3 线程安全问题的条件)
- 六、synchronized关键字
-
- [6.1 同步方法](#6.1 同步方法)
- [6.2 同步代码块](#6.2 同步代码块)
- [6.3 同步方法 vs 同步代码块](#6.3 同步方法 vs 同步代码块)
- [6.4 死锁问题](#6.4 死锁问题)
- 七、线程间通信
-
- [7.1 wait/notify机制](#7.1 wait/notify机制)
- [7.2 wait/notify注意事项](#7.2 wait/notify注意事项)
- 八、生产者消费者模式
- 九、常见面试题解析
- 十、总结与下篇预告
-
- 本篇要点
- [📢 下篇预告](#📢 下篇预告)
- [💬 互动话题](#💬 互动话题)
⚡ 26 多线程基础------Thread、Runnable与线程安全
更新日期 :2026年5月 | Java入门到精通系列 · 第四阶段·高级特性
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。
一、线程概述
1.1 进程与线程
| 概念 | 说明 |
|---|---|
| 进程 | 操作系统分配资源的基本单位,一个进程至少有一个线程 |
| 线程 | CPU调度的基本单位,是进程中的执行单元 |
| 单线程 | 程序只有一条执行路径(main方法就是单线程) |
| 多线程 | 程序有多条并发执行的路径 |
1.2 为什么使用多线程
场景1:下载多个文件
单线程:下载完A → 下载完B → 下载完C (串行,耗时长)
多线程:同时下载A、B、C (并行,效率高)
场景2:GUI程序
主线程处理用户交互
后台线程执行耗时任务(避免界面卡顿)
二、创建线程的方式
2.1 方式一:继承Thread类
java
/**
* 方式一:继承Thread类
*/
public class MyThread extends Thread {
private String threadName;
public MyThread(String name) {
this.threadName = name;
}
@Override
public void run() {
// 线程要执行的任务
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " 执行第 " + i + " 次");
try {
Thread.sleep(500); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread("线程A");
MyThread t2 = new MyThread("线程B");
// 启动线程(注意:是调用start(),不是run())
t1.start();
t2.start();
// 如果直接调用run(),则在当前线程中执行,不是多线程
// t1.run(); // ❌ 这不是多线程!
}
}
2.2 方式二:实现Runnable接口(推荐)
java
/**
* 方式二:实现Runnable接口
*/
public class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(taskName + " - 第 " + i + " 次");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建Runnable对象
MyRunnable task1 = new MyRunnable("任务A");
MyRunnable task2 = new MyRunnable("任务B");
// 包装为Thread对象并启动
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
2.3 方式三:实现Callable接口(有返回值)
java
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 方式三:实现Callable接口(可以有返回值和抛出异常)
*/
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= number; i++) {
sum += i;
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " 计算完成");
return sum;
}
public static void main(String[] args) throws Exception {
// 创建Callable对象
MyCallable callable = new MyCallable(100);
// 使用FutureTask包装Callable
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 创建并启动线程
Thread thread = new Thread(futureTask, "计算线程");
thread.start();
// 做其他事情...
System.out.println("主线程在做其他事情...");
// 获取返回值(阻塞等待)
Integer result = futureTask.get();
System.out.println("计算结果:" + result); // 5050
}
}
2.4 三种方式对比
| 特性 | 继承Thread | 实现Runnable | 实现Callable |
|---|---|---|---|
| 返回值 | 无 | 无 | 有(泛型指定) |
| 异常 | 不能抛出检查异常 | 不能抛出检查异常 | 可以抛出检查异常 |
| 继承限制 | 单继承 | 可以实现多个接口 | 可以实现多个接口 |
| 资源共享 | 需要注意 | 天然支持(同一Runnable) | 天然支持 |
| 推荐程度 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
三、线程的生命周期
3.1 线程状态
start()
NEW ───────────────────────────> RUNNABLE
(新建) (可运行)
│
┌────────────────┼────────────────┐
│ │ │
wait()/ sleep()/ synchronized
join() wait(timeout) 等待锁
│ │ │
▼ ▼ ▼
WAITING TIMED_WAITING BLOCKED
(无限等待) (超时等待) (阻塞)
│ │ │
│ notify()/ │ 超时/ │ 获取锁
│ notifyAll() │ notify() │
│ │ │
└────────────────┼────────────────┘
│
▼
RUNNABLE
│
run()结束
│
▼
TERMINATED
(终止)
3.2 线程状态查询
java
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// NEW状态
System.out.println("创建后:" + thread.getState()); // NEW
thread.start();
System.out.println("启动后:" + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("sleep中:" + thread.getState()); // TIMED_WAITING
thread.join(); // 等待thread执行完毕
System.out.println("结束后:" + thread.getState()); // TERMINATED
}
}
四、Thread类常用方法
4.1 常用方法一览
| 方法 | 说明 |
|---|---|
start() |
启动线程,JVM调用run()方法 |
run() |
线程执行的任务代码 |
setName(String) |
设置线程名称 |
getName() |
获取线程名称 |
setPriority(int) |
设置优先级(1-10) |
getPriority() |
获取优先级 |
setDaemon(boolean) |
设置为守护线程 |
sleep(long ms) |
让当前线程休眠指定毫秒 |
join() |
等待该线程执行完毕 |
join(long ms) |
等待该线程执行完毕或超时 |
yield() |
让出CPU时间片 |
interrupt() |
中断线程 |
isInterrupted() |
检查是否被中断 |
isAlive() |
检查线程是否存活 |
currentThread() |
获取当前线程对象 |
4.2 线程命名与获取
java
public class ThreadNameDemo {
public static void main(String[] args) {
// 方式1:构造方法设置名称
Thread t1 = new Thread("下载线程") {
@Override
public void run() {
System.out.println("我是:" + getName());
}
};
// 方式2:setName设置
Thread t2 = new Thread();
t2.setName("上传线程");
// 方式3:构造方法指定名称
Thread t3 = new Thread(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
}, "处理线程");
t1.start();
t3.start();
// 默认线程名
System.out.println("主线程名:" + Thread.currentThread().getName()); // main
}
}
4.3 线程休眠(sleep)
java
public class SleepDemo {
public static void main(String[] args) {
// 倒计时效果
for (int i = 5; i >= 0; i--) {
System.out.println("倒计时:" + i + " 秒");
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("⏰ 时间到!");
}
}
4.4 线程加入(join)
java
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread downloadThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("下载进度:" + (i * 20) + "%");
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
System.out.println("✅ 下载完成!");
}, "下载线程");
downloadThread.start();
// 等待下载线程执行完毕
downloadThread.join();
// 只有下载完成后才执行到这里
System.out.println("📂 开始处理下载的文件...");
}
}
4.5 守护线程(Daemon)
java
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try { Thread.sleep(300); } catch (InterruptedException e) {}
}
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
Thread.sleep(1000);
System.out.println("主线程结束,守护线程也会自动结束");
// 当所有非守护线程结束时,守护线程会自动终止
}
}
4.6 线程中断
java
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
// 检查中断标志
if (Thread.currentThread().isInterrupted()) {
System.out.println("收到中断信号,线程退出");
return;
}
System.out.println("执行中:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// sleep被中断时会抛出异常并清除中断标志
System.out.println("sleep被中断,退出线程");
return;
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt(); // 发送中断信号
}
}
五、线程安全问题
5.1 什么是线程安全问题
java
/**
* 线程安全问题演示:卖票场景
*/
public class UnsafeTicketDemo implements Runnable {
private int tickets = 100; // 共100张票
@Override
public void run() {
while (true) {
if (tickets > 0) {
try {
Thread.sleep(10); // 模拟出票耗时
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()
+ " 卖出第 " + tickets + " 张票");
tickets--;
} else {
break;
}
}
}
public static void main(String[] args) {
UnsafeTicketDemo demo = new UnsafeTicketDemo();
// 3个窗口(线程)共享同一个Runnable对象
new Thread(demo, "窗口1").start();
new Thread(demo, "窗口2").start();
new Thread(demo, "窗口3").start();
// 可能出现:
// - 卖出负数票
// - 同一张票被卖出多次(重复票)
// - 数据不一致
}
}
5.2 线程安全问题的原因
问题本质:多线程同时访问共享数据时,操作不是原子的
tickets = 1 的场景:
线程1:读取tickets(1) → 判断tickets > 0 ✓ → sleep → 输出 → tickets = 0
线程2:读取tickets(1) → 判断tickets > 0 ✓ → sleep → 输出 → tickets = 0 ← 问题!
根本原因:
1. 多线程并发访问共享数据
2. 操作不是原子的(读取-判断-修改不是一步完成)
3. CPU时间片切换导致指令交错执行
5.3 线程安全问题的条件
| 条件 | 说明 |
|---|---|
| 多线程环境 | 至少两个线程 |
| 共享数据 | 多个线程访问同一个变量 |
| 数据修改 | 至少有一个线程在修改数据 |
破坏任一条件即可解决线程安全问题。
六、synchronized关键字
6.1 同步方法
java
/**
* 使用synchronized解决线程安全问题
*/
public class SafeTicketDemo implements Runnable {
private int tickets = 100;
// 同步方法:锁对象是this
@Override
public synchronized void run() {
// 方式有问题:整个run方法被锁住,一个线程执行完才能轮到下一个
// 应该只锁住关键代码
}
// 正确方式:只同步关键代码
@Override
public void runCorrected() {
while (true) {
synchronized (this) { // 同步代码块
if (tickets > 0) {
try { Thread.sleep(10); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()
+ " 卖出第 " + tickets + " 张票");
tickets--;
} else {
break;
}
}
}
}
}
6.2 同步代码块
java
public class SynchronizedBlockDemo implements Runnable {
private int tickets = 100;
private Object lock = new Object(); // 锁对象
@Override
public void run() {
while (true) {
synchronized (lock) { // 使用同步代码块
if (tickets > 0) {
try { Thread.sleep(10); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()
+ " 卖出第 " + tickets + " 张票");
tickets--;
} else {
break;
}
}
}
}
public static void main(String[] args) {
SynchronizedBlockDemo demo = new SynchronizedBlockDemo();
new Thread(demo, "窗口1").start();
new Thread(demo, "窗口2").start();
new Thread(demo, "窗口3").start();
// 现在不会出现重复票和负数票了
}
}
6.3 同步方法 vs 同步代码块
| 特性 | 同步方法 | 同步代码块 |
|---|---|---|
| 锁对象 | this(当前对象) | 可以指定任意对象 |
| 范围 | 整个方法 | 精确到某段代码 |
| 性能 | 较差(锁范围大) | 较好(锁范围小) |
| 使用场景 | 方法内全部是关键代码 | 只有部分代码需要同步 |
6.4 死锁问题
java
/**
* 死锁演示
*/
public class DeadLockDemo {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();
public static void main(String[] args) {
// 线程1:先锁A,再锁B
new Thread(() -> {
synchronized (LOCK_A) {
System.out.println("线程1:获取锁A");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (LOCK_B) {
System.out.println("线程1:获取锁B");
}
}
}).start();
// 线程2:先锁B,再锁A(与线程1相反,容易死锁)
new Thread(() -> {
synchronized (LOCK_B) {
System.out.println("线程2:获取锁B");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (LOCK_A) {
System.out.println("线程2:获取锁A");
}
}
}).start();
// 结果:线程1持有A等待B,线程2持有B等待A → 死锁!
}
}
避免死锁的方法:
| 方法 | 说明 |
|---|---|
| 固定加锁顺序 | 所有线程按相同顺序获取锁 |
| 设置超时 | tryLock(timeout)避免无限等待 |
| 减少锁粒度 | 只在必要时加锁 |
| 使用jps+jstack | 检测死锁 |
七、线程间通信
7.1 wait/notify机制
java
/**
* wait/notify线程间通信
*/
public class WaitNotifyDemo {
private boolean hasData = false;
private int data;
// 生产者
public synchronized void produce(int value) {
while (hasData) {
try {
wait(); // 有数据则等待消费者消费
} catch (InterruptedException e) {}
}
data = value;
hasData = true;
System.out.println("生产:" + value);
notify(); // 通知消费者
}
// 消费者
public synchronized int consume() {
while (!hasData) {
try {
wait(); // 无数据则等待生产者生产
} catch (InterruptedException e) {}
}
hasData = false;
System.out.println("消费:" + data);
notify(); // 通知生产者
return data;
}
}
7.2 wait/notify注意事项
| 方法 | 说明 |
|---|---|
wait() |
使当前线程等待,释放锁 |
wait(long timeout) |
等待指定毫秒,超时自动唤醒 |
notify() |
唤醒一个等待的线程 |
notifyAll() |
唤醒所有等待的线程 |
注意事项:
wait/notify必须在synchronized块中调用- 使用
while循环检查条件(防止虚假唤醒) notifyAll()比notify()更安全
八、生产者消费者模式
java
/**
* 完整的生产者消费者模式
*/
public class ProducerConsumerDemo {
private static final int MAX_SIZE = 10;
private List<Integer> buffer = new ArrayList<>();
private Object lock = new Object();
// 生产者
class Producer extends Thread {
@Override
public void run() {
while (true) {
synchronized (lock) {
while (buffer.size() >= MAX_SIZE) {
try { lock.wait(); } catch (InterruptedException e) {}
}
int value = (int) (Math.random() * 100);
buffer.add(value);
System.out.println("生产:" + value + " | 库存:" + buffer.size());
lock.notifyAll();
}
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
}
// 消费者
class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (lock) {
while (buffer.isEmpty()) {
try { lock.wait(); } catch (InterruptedException e) {}
}
int value = buffer.remove(0);
System.out.println("消费:" + value + " | 库存:" + buffer.size());
lock.notifyAll();
}
try { Thread.sleep(150); } catch (InterruptedException e) {}
}
}
}
public static void main(String[] args) {
ProducerConsumerDemo demo = new ProducerConsumerDemo();
demo.new Producer().start();
demo.new Producer().start();
demo.new Consumer().start();
demo.new Consumer().start();
}
}
九、常见面试题解析
Q1:start()和run()的区别?
start()启动新线程,JVM会自动调用run()方法;直接调用run()只是普通方法调用,在当前线程中执行,不会创建新线程。
Q2:synchronized的原理是什么?
synchronized基于对象的监视器锁(Monitor Lock)。每个对象都有一个锁,线程进入synchronized块时获取锁,退出时释放锁,保证同一时刻只有一个线程执行同步代码。
Q3:如何停止一个线程?
不推荐使用
stop()(已废弃,不安全)。推荐使用中断机制 (interrupt()+ 检查中断标志)或通过共享标志位控制线程退出。
十、总结与下篇预告
本篇要点
| 要点 | 说明 |
|---|---|
| 创建线程 | Thread、Runnable、Callable三种方式 |
| 线程状态 | NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED |
| 线程安全 | 多线程 + 共享数据 + 修改 = 不安全 |
| synchronized | 同步方法/同步代码块解决线程安全 |
| 死锁 | 两个线程互相等待对方持有的锁 |
| wait/notify | 线程间通信机制 |
📢 下篇预告
第27篇:多线程进阶------我们将学习线程池、Lock锁、volatile、并发工具类和CompletableFuture!
💬 互动话题
你在实际开发中遇到过多线程问题吗?最头疼的是线程安全还是死锁?欢迎在评论区分享你的经历!
参考资料: