多线程详解
一、先搞懂:什么是多线程?
1.1 通俗理解
多线程 = 一个程序同时干多件事,就像你 一边听音乐、一边刷手机、一边喝水 —— 不是“做完一件再做另一件”,而是“同时进行”(本质是CPU快速切换,视觉上同步)。
举个Java程序的例子:
-
单线程:打开一个记事本,只能先打字,再保存,不能同时打字和保存
-
多线程:打开微信,既能同时接收消息,又能发朋友圈、听语音,互不影响
核心目的:提高程序效率,避免某一个操作(比如等待网络、读取文件)卡住整个程序。
1.2 进程 vs 线程
很多新手会混淆这两个概念,用一句话说清:
进程是“一个完整的程序”(比如微信、记事本),线程是“进程里的一个任务”(比如微信接收消息、发朋友圈)
| 对比项 | 进程 | 线程 |
|---|---|---|
| 本质 | 独立的程序单元 | 进程内的执行任务 |
| 资源占用 | 多(独立内存、CPU) | 少(共享进程资源) |
| 切换速度 | 慢 | 快 |
简单记:一个进程可以包含多个线程,线程是进程的“小弟”,共享大哥的资源,一起干活。
二、Java 中创建多线程的 3 种方式
Java 提供了 3 种创建线程的方式,从简单到复杂排序,新手先掌握前 2 种即可。
2.1 方式一:继承 Thread 类(最基础)
步骤:① 继承 Thread 类 ② 重写 run() 方法(线程要干的活) ③ 创建对象,调用 start() 方法启动线程
完整可运行代码(复制就能测):
// 1. 继承 Thread 类
class MyThread extends Thread {
// 2. 重写 run() 方法:线程要执行的逻辑
@Override
public void run() {
// 这里写线程要干的活,比如循环打印
for (int i = 0; i < 5; i++) {
// Thread.currentThread().getName() 获取当前线程名称
System.out.println(Thread.currentThread().getName() + ":执行第 " + (i+1) + " 次");
try {
// 让线程休眠 500 毫秒(模拟干活耗时),新手可忽略
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 创建线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 4. 给线程起名字(可选,方便区分)
thread1.setName("线程A");
thread2.setName("线程B");
// 5. 启动线程(必须用 start(),不能用 run()!)
thread1.start();
thread2.start();
}
}
运行结果(参考):
线程A:执行第 1 次 线程B:执行第 1 次 线程A:执行第 2 次 线程B:执行第 2 次 线程A:执行第 3 次 线程B:执行第 3 次 线程A:执行第 4 次 线程B:执行第 4 次 线程A:执行第 5 次 线程B:执行第 5 次
新手注意:启动线程必须用 start\(\) 方法,不能直接调用 run\(\) —— 调用 run\(\) 只是普通方法调用,不会开启多线程,还是单线程执行。
2.2 方式二:实现 Runnable 接口(推荐)
步骤:① 实现 Runnable 接口 ② 重写 run() 方法 ③ 创建 Runnable 实现类对象 ④ 把对象传给 Thread 类,调用 start() 启动
为什么推荐?Java 只能单继承,继承 Thread 类后就不能继承其他类了,而实现接口可以多实现,更灵活。
完整可运行代码:
// 1. 实现 Runnable 接口
class MyRunnable implements Runnable {
// 2. 重写 run() 方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":执行第 " + (i+1) + " 次");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public class ThreadDemo2 {
public static void main(String[] args) {
// 3. 创建 Runnable 实现类对象
MyRunnable runnable = new MyRunnable();
// 4. 把对象传给 Thread,创建线程
Thread thread1 = new Thread(runnable, "线程C");
Thread thread2 = new Thread(runnable, "线程D");
// 5. 启动线程
thread1.start();
thread2.start();
}
}
运行结果和方式一类似,都是两个线程交替执行,核心区别是“实现接口”更灵活。
2.3 方式三:实现 Callable 接口(带返回值,进阶)
前两种方式的 run() 方法没有返回值、不能抛异常,Callable 接口解决了这个问题,适合需要获取线程执行结果的场景(新手了解即可)。
完整可运行代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 实现 Callable 接口,指定返回值类型(这里是 Integer)
class MyCallable implements Callable<Integer> {
// 2. 重写 call() 方法(有返回值、可抛异常)
@Override
public Integer call() throws Exception {
int sum = 0;
// 线程任务:计算 1-5 的和
for (int i = 1; i <= 5; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":计算 i=" + i);
Thread.sleep(500);
}
return sum; // 返回计算结果
}
}
// 测试类
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 创建 Callable 实现类对象
MyCallable callable = new MyCallable();
// 4. 包装成 FutureTask(用于获取返回值)
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 5. 启动线程
Thread thread = new Thread(futureTask, "计算线程");
thread.start();
// 6. 获取线程返回值(会阻塞,直到线程执行完成)
Integer result = futureTask.get();
System.out.println("线程执行结果:1-5 的和 = " + result);
}
}
运行结果会先打印计算过程,最后输出总和 15,适合需要获取线程执行结果的场景(比如多线程计算任务)。
三、多线程的核心问题:线程安全
3.1 什么是线程安全?
多个线程同时操作同一个资源(比如一个变量、一个文件)时,会出现“数据错乱”的问题,这就是线程不安全。
举个例子:两个线程同时给一个变量 count 加 1,预期结果是 2,但实际可能是 1(因为两个线程同时读取到 0,都加 1 后变成 1)。
线程不安全演示代码(复制测试,会出现数据错乱):
class UnsafeThread implements Runnable {
// 共享资源:两个线程同时操作这个变量
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
count++; // 线程不安全的操作
}
}
// 获取最终结果
public int getCount() {
return count;
}
}
public class ThreadUnsafeDemo {
public static void main(String[] args) throws InterruptedException {
UnsafeThread unsafeThread = new UnsafeThread();
// 两个线程同时操作 count
Thread t1 = new Thread(unsafeThread);
Thread t2 = new Thread(unsafeThread);
t1.start();
t2.start();
// 等待两个线程执行完成(新手可忽略,用于确保结果准确)
t1.join();
t2.join();
// 预期结果:2000,实际结果大概率小于 2000(数据错乱)
System.out.println("最终 count 值:" + unsafeThread.getCount());
}
}
运行后会发现,最终 count 值往往小于 2000,这就是线程不安全的问题。
3.2 新手必学:解决线程安全的 2 种简单方式
方式一:synchronized 关键字(锁)
通俗理解:给“共享资源的操作”加一把锁,同一时间只有一个线程能执行这个操作,其他线程排队等待。
修改上面的代码,添加 synchronized,实现线程安全:
class SafeThread implements Runnable {
private int count = 0;
// 方式1:给方法加锁(整个方法都被锁住)
@Override
public synchronized void run() {
for (int i = 0; i < 1000; i++) {
count++;
}
}
// 方式2:给代码块加锁(只锁共享资源操作,更高效)
// @Override
// public void run() {
// synchronized (this) { // this 表示当前对象,锁的是共享资源所在的对象
// for (int i = 0; i < 1000; i++) {
// count++;
// }
// }
// }
public int getCount() {
return count;
}
}
public class ThreadSafeDemo {
public static void main(String[] args) throws InterruptedException {
SafeThread safeThread = new SafeThread();
Thread t1 = new Thread(safeThread);
Thread t2 = new Thread(safeThread);
t1.start();
t2.start();
t1.join();
t2.join();
// 此时结果一定是 2000,线程安全
System.out.println("最终 count 值:" + safeThread.getCount());
}
}
新手建议:先掌握“给方法加锁”,简单易记;后续再学习“代码块加锁”(更高效)。
方式二:使用线程安全的类
Java 自带一些线程安全的类,比如 AtomicInteger(原子类),专门用于解决“变量自增/自减”的线程安全问题,不用自己加锁。
示例代码:
import java.util.concurrent.atomic.AtomicInteger;
class AtomicThread implements Runnable {
// 线程安全的原子类,替代普通 int
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
count.incrementAndGet(); // 原子自增,线程安全
}
}
public int getCount() {
return count.get(); // 获取值
}
}
public class AtomicDemo {
public static void main(String[] args) throws InterruptedException {
AtomicThread atomicThread = new AtomicThread();
Thread t1 = new Thread(atomicThread);
Thread t2 = new Thread(atomicThread);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终 count 值:" + atomicThread.getCount()); // 一定是 2000
}
}
四、新手必记:多线程核心总结
-
多线程本质:一个程序同时执行多个任务,提高效率(CPU快速切换,视觉上同步)。
-
创建方式:
-
继承 Thread 类:简单,不能多继承
-
实现 Runnable 接口:推荐,灵活可多实现
-
实现 Callable 接口:进阶,带返回值、可抛异常
-
-
线程安全:
-
问题:多个线程操作同一资源,导致数据错乱
-
解决:
synchronized加锁 或 使用线程安全类(如 AtomicInteger)
-
-
新手避坑:
-
启动线程用
start\(\),不是run\(\) -
不要让多个线程操作同一个普通变量(避免线程不安全)
-
线程休眠用
Thread\.sleep\(毫秒\),单位是毫秒
-
五、新手学习建议
1. 先把前两种创建线程的方式,逐行敲一遍代码,观察线程交替执行的效果,理解多线程的“并行”感;
2. 测试线程不安全的代码,看看数据错乱的现象,再用synchronized 修复,感受“锁”的作用;
3. 不用急于学复杂的线程池、锁机制,先掌握基础用法,后续学习 Spring、MyBatis 时,会遇到多线程的实际应用;
4. 尝试写一个简单的多线程案例:比如两个线程分别打印 1-10,观察交替执行的结果。
博客结尾
多线程是 Java 基础中的重点,也是新手的难点,但只要结合生活案例 + 多敲代码,就能轻松理解。
如果运行代码时遇到问题,或者有不懂的地方,欢迎在评论区留言,一起交流学习~


被折叠的 条评论
为什么被折叠?



