26 多线程基础——Thread、Runnable与线程安全

目录

  • [⚡ 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() 唤醒所有等待的线程

注意事项:

  1. wait/notify必须在synchronized块中调用
  2. 使用while循环检查条件(防止虚假唤醒)
  3. 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!

💬 互动话题

你在实际开发中遇到过多线程问题吗?最头疼的是线程安全还是死锁?欢迎在评论区分享你的经历!


参考资料

相关推荐
轮子飞了1 小时前
Spring Ai 集成 DashScope 多模态模型实现身份证信息识别
java·人工智能·spring
lulu12165440781 小时前
大模型API聚合平台技术架构深度对比:六大平台协议转换、路由调度与安全治理全解析 - 微元算力(weytoken)
java·人工智能·安全·架构·ai编程
可乐ea1 小时前
【Spring Boot + MyBatis|第4篇】MyBatis 动态 SQL:if、where、foreach 使用详解
java·spring boot·后端·sql·mybatis
記億揺晃着的那天1 小时前
Windows 通过 Java 获取可用端口的一个坑:Hyper-V 保留端口导致 UDP 绑定失败
java·windows·udp
组合缺一1 小时前
SolonCode(编码智能体)支持鸿蒙 PC
java·华为·ai·ai编程·harmonyos·solon·soloncode
小bo波1 小时前
用匿名内部类优雅地计算方法执行时间
java·设计模式·性能测试·模板方法模式·lambda·代码优化·匿名内部类
折哥的程序人生 · 物流技术专研1 小时前
Tomcat 严重警告:JDBC 驱动未注销 + 工作线程泄漏 —— 原因、影响与彻底修复(生产级终极指南)
java·运维·数据库·mysql·oracle·tomcat
一个儒雅随和的男子1 小时前
sentinel底层原理剖析以及实战优化
java·网络·sentinel
两年半的个人练习生^_^1 小时前
JMM 进阶:彻底理解 synchronized 实现原理
java·开发语言