欢迎光临
我们一直在努力

Java多线程:从0到1教你理解并掌握多线程,只看这一篇文章就够了!

目录

一、先搞懂:多线程到底是什么?(核心概念)

二、实战第一步:Java 创建多线程的 2 种核心方式

方式 1:继承 Thread 类(入门首选,代码简单)

核心逻辑

完整可运行代码

运行效果(参考)

易错点提醒

方式 2:实现 Runnable 接口(实战推荐,无继承限制)

完整可运行代码

两种方式对比

三、多线程的 “坑”:线程安全问题(面试 / 实战高频)

第一步:重现问题(模拟售票系统)

运行结果(错误示例)

问题原因

第二步:解决问题 —— 同步锁(synchronized)

方式 1:同步方法(给方法加锁)

方式 2:同步代码块(更灵活)

关键提醒

四、必懂:线程的生命周期(面试常考)

核心方法对应的状态切换

五、进阶:实战必用的 JUC 工具类

1. Callable + Future(带返回值的线程)

2. 线程池(实战首选,复用线程)

常用线程池类型

总结


作为编程学习者,你肯定想系统掌握 Java 多线程的核心知识,既能理解底层逻辑,又能上手实战。这篇内容我会彻底抛弃晦涩术语,用 “概念 + 代码 + 实战避坑” 的模式,从 0 到 1 帮你吃透多线程,看完就能写代码、解问题。

一、先搞懂:多线程到底是什么?(核心概念)

先把核心概念掰碎,用生活例子讲明白,拒绝抽象:

  • 进程:正在运行的程序(比如你打开的微信、IDEA),是操作系统分配内存、CPU 等资源的基本单位。类比:你去餐厅吃饭,“餐厅里的你” 就是一个进程,占用了餐桌、餐具这些资源。
  • 线程:进程内的最小执行单元(比如微信同时收消息、刷朋友圈、发语音),是 CPU 调度的最小单位。类比:你吃饭时 “夹菜、聊天、玩手机”,每一个动作就是一个线程,共享 “你的时间、餐桌” 这些进程资源。
  • 多线程:一个进程里同时跑多个线程,实现 “并发”(看起来同时执行,实际 CPU 快速切换线程)。关键:线程共享进程资源,这是多线程的核心优势,也是 “线程安全问题” 的根源。

二、实战第一步:Java 创建多线程的 2 种核心方式

Java 封装了现成的 API,新手先掌握这 2 种方式,就能快速写出多线程程序,重点记住:启动线程必须用start(),不是直接调run()。

方式 1:继承 Thread 类(入门首选,代码简单)

核心逻辑

继承Thread类 → 重写run()(定义线程要做的事) → 创建线程对象 → 调用start()启动。

完整可运行代码

// 1. 自定义线程类,继承Thread
class MyThread extends Thread {
// 2. 重写run():线程要执行的核心逻辑
@Override
public void run() {
// 模拟线程任务:循环输出5次
for (int i = 0; i < 5; i++) {
// 获取当前线程名称,方便观察执行顺序
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行:" + i);

// 模拟耗时操作(让线程切换更明显)
try {
Thread.sleep(100); // 线程休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 测试类(主线程)
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 创建2个线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();

// 给线程命名(可选,但建议写,方便调试)
t1.setName("线程A");
t2.setName("线程B");

// 4. 启动线程(核心:start()会创建新线程)
t1.start();
t2.start();

// 主线程也执行逻辑,对比“并发”效果
for (int i = 0; i < 5; i++) {
System.out.println("主线程 执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

运行效果(参考)

主线程 执行:0
线程A 执行:0
线程B 执行:0
主线程 执行:1
线程B 执行:1
线程A 执行:1
…(交替输出,体现多线程并发)

易错点提醒

❌ 不要直接调用run():调用run()只是普通方法调用,不会创建新线程,所有逻辑都会在主线程串行执行,等于白写多线程。

方式 2:实现 Runnable 接口(实战推荐,无继承限制)

Java 是 “单继承” 语言,如果你的类已经继承了其他类(比如BaseService),就没法再继承Thread了 —— 实现Runnable是更灵活的选择,实现 “任务逻辑” 和 “线程对象” 解耦。

完整可运行代码

// 1. 自定义任务类,实现Runnable接口
class MyRunnable implements Runnable {
// 2. 重写run():定义任务逻辑
@Override
public void run() {
for (int i = 0; i < 5; i++) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 测试类
public class ThreadDemo2 {
public static void main(String[] args) {
// 3. 创建任务对象(只定义“要做什么”)
MyRunnable task = new MyRunnable();

// 4. 创建线程对象,把任务传入(线程负责“执行任务”)
Thread t1 = new Thread(task, "线程1");
Thread t2 = new Thread(task, "线程2");

// 5. 启动线程
t1.start();
t2.start();
}
}

两种方式对比
实现方式优点缺点推荐度
继承 Thread 代码简单,直接调用 start () 单继承限制,任务和线程耦合 ★★★☆☆
实现 Runnable 无继承限制,任务可复用 代码略繁琐(需多创建任务对象) ★★★★★

三、多线程的 “坑”:线程安全问题(面试 / 实战高频)

当多个线程同时操作共享资源(比如同一个变量、同一个对象),就会出现 “数据错乱”—— 这是多线程最核心的难点,也是新手最容易踩的坑。

第一步:重现问题(模拟售票系统)

场景:10 张票,3 个窗口同时卖,预期每张票只卖一次,实际会出问题。

// 共享资源:售票机(10张票)
class Ticket implements Runnable {
private int ticketNum = 10; // 所有窗口共用这10张票

@Override
public void run() {
while (true) {
if (ticketNum > 0) {
// 模拟售票耗时(比如用户付钱)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticketNum + " 张票");
ticketNum–; // 票数减1
} else {
break;
}
}
}
}

// 测试类
public class ThreadSafeDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread window1 = new Thread(ticket, "窗口1");
Thread window2 = new Thread(ticket, "窗口2");
Thread window3 = new Thread(ticket, "窗口3");

window1.start();
window2.start();
window3.start();
}
}

运行结果(错误示例)

窗口1 卖出第 10 张票
窗口2 卖出第 10 张票 // 重复售票(错误)
窗口3 卖出第 9 张票
窗口1 卖出第 8 张票
窗口3 卖出第 0 张票 // 卖出0张票(错误)

问题原因

3 个线程同时读取ticketNum=10,然后同时执行ticketNum–,导致重复售票;或者某个线程还没来得及减 1,其他线程已经把票数减到 0,出现 “0 张票” 的错误。

第二步:解决问题 —— 同步锁(synchronized)

核心思路:给共享资源 “加锁”,同一时间只有一个线程能拿到锁,操作完释放锁,其他线程再排队。有 2 种常用方式,按需选择:

方式 1:同步方法(给方法加锁)

把操作共享资源的代码封装成synchronized修饰的方法,锁对象是this(当前共享资源对象)。

class Ticket implements Runnable {
private int ticketNum = 10;

// 同步方法:synchronized修饰,自动加锁/释放锁
private synchronized void sellTicket() {
if (ticketNum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticketNum + " 张票");
ticketNum–;
}
}

@Override
public void run() {
while (true) {
if (ticketNum <= 0) break;
sellTicket(); // 调用同步方法
}
}
}

方式 2:同步代码块(更灵活)

只给 “核心代码” 加锁,指定自定义锁对象(必须是多线程共享的同一个)。

class Ticket implements Runnable {
private int ticketNum = 10;
// 自定义锁对象(核心:多个线程共用这一个锁)
private Object lock = new Object();

@Override
public void run() {
while (true) {
// 同步代码块:只锁核心逻辑,减少锁范围
synchronized (lock) {
if (ticketNum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticketNum + " 张票");
ticketNum–;
} else {
break;
}
}
}
}
}

关键提醒

✅ 锁对象必须是多个线程共享的同一个:如果每个线程都创建自己的锁对象,锁会失效,还是会出现线程安全问题。

四、必懂:线程的生命周期(面试常考)

Java 线程从创建到销毁有 6 种状态,理解状态切换,能避免 “线程卡死”“无法启动” 等问题:

  • 新建(New):创建 Thread 对象,但未调用start();
  • 就绪(Runnable):调用start()后,等待 CPU 调度;
  • 运行(Running):CPU 调度线程,执行run();
  • 阻塞(Blocked):等待锁、IO 操作等,释放 CPU;
  • 等待 / 超时等待(Waiting/Timed Waiting):wait()(无限等)、sleep(1000)(限时等);
  • 终止(Terminated):run()执行完或异常终止。
  • 核心方法对应的状态切换

    • start():新建 → 就绪;
    • sleep(long):运行 → 超时等待 → 就绪(时间到自动切换);
    • wait():运行 → 等待;notify()/notifyAll():等待 → 就绪;
    • join():当前线程阻塞,直到目标线程执行完毕。

    五、进阶:实战必用的 JUC 工具类

    Java 的java.util.concurrent(JUC)包封装了更高效的多线程工具,新手先掌握这 2 个,就能应对大部分实战场景。

    1. Callable + Future(带返回值的线程)

    Runnable的run()无返回值,Callable的call()可返回结果,结合Future获取执行结果。

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;

    // 实现Callable,指定返回值类型(Integer)
    class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
    // 模拟计算任务:1-100的和
    int sum = 0;
    for (int i = 1; i <= 100; i++) {
    sum += i;
    }
    return sum; // 返回结果
    }
    }

    public class CallableDemo {
    public static void main(String[] args) {
    // 1. 创建Callable任务
    MyCallable task = new MyCallable();
    // 2. 封装成FutureTask(用于获取结果)
    FutureTask<Integer> futureTask = new FutureTask<>(task);
    // 3. 启动线程
    new Thread(futureTask, "计算线程").start();

    // 4. 获取结果(get()会阻塞,直到线程执行完)
    try {
    Integer result = futureTask.get();
    System.out.println("计算结果:" + result); // 输出5050
    } catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
    }
    }
    }

    2. 线程池(实战首选,复用线程)

    频繁创建 / 销毁线程会耗资源,线程池复用线程,提升性能,核心用Executors快速创建:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    public class ThreadPoolDemo {
    public static void main(String[] args) {
    // 1. 创建固定大小线程池(3个线程)
    ExecutorService pool = Executors.newFixedThreadPool(3);

    // 2. 提交5个任务(线程池复用3个线程执行)
    for (int i = 1; i <= 5; i++) {
    int taskNum = i;
    pool.submit(() -> {
    System.out.println(Thread.currentThread().getName() + " 执行任务" + taskNum);
    try {
    Thread.sleep(100);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    });
    }

    // 3. 关闭线程池(用完必须关,否则程序不退出)
    pool.shutdown();
    }
    }

    常用线程池类型
    • newFixedThreadPool(n):固定大小,适合任务量稳定;
    • newCachedThreadPool():动态扩容,适合短任务、任务量波动大;
    • newSingleThreadExecutor():单线程,任务串行执行;
    • newScheduledThreadPool(n):定时 / 周期性任务(比如每隔 1 秒执行)。

    总结

  • 核心实现:创建多线程有 2 种方式 —— 继承 Thread(入门)、实现 Runnable(推荐),启动必须用start();
  • 线程安全:多线程操作共享资源需加synchronized(同步方法 / 代码块),锁对象必须共用;
  • 进阶工具:Callable+Future 实现带返回值线程,线程池复用线程提升性能,是实战首选。
  • 掌握这些内容,你已经能搞定 Java 多线程的基础开发和面试核心考点,后续可深入学习Lock锁、线程通信、死锁避免等进阶知识点。记住:多线程的核心是 “实践”,多写代码、多测试,才能真正吃透!文章如有错误欢迎私信我,我会及时解决,如果我的内容对你有帮助和启发,请点赞、评论、收藏。你们的支持就是我更新最大的动力,那么我们下期再见!

    赞(0)
    未经允许不得转载:171主机测评 » Java多线程:从0到1教你理解并掌握多线程,只看这一篇文章就够了!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址