视频原地址:【狂神说Java】多线程详解哔哩哔哩bilibili

笔记优化说明:补充完整可运行代码、核心原理解释、使用场景和避坑点,适配新手理解和实际开发场景。

一.创建多线程3 种核心方式(附完整代码 + 使用场景)

1.继承Thread类(不推荐,单继承限制)

  • 步骤:继承Thread类 → 重写run()方法(线程执行体) → 创建实例调用start()启动(不能直接调用run(),否则只是普通方法)

  • 缺点:Java 单继承特性,继承 Thread 后无法继承其他类,灵活性差。

  • 案例是主方法线程逻辑与线程竞争

 // 1. 继承Thread类
 public class TestThread01 extends Thread {
     // 2. 重写run方法(线程要执行的逻辑)
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
             System.out.println("子线程执行:" + i);
         }
     }
 ​
     // 主方法(程序入口)
     public static void main(String[] args) {
         // 3. 创建线程实例
         TestThread01 thread = new TestThread01();
         // 4. 调用start()启动线程(JVM会自动调用run方法)
         thread.start();
 ​
         // 5.主线程逻辑
         for (int i = 0; i < 5; i++) {
             System.out.println("主线程执行:" + i);
         }
     }
 }

2.实现MyRunnable接口(推荐,无单继承限制)

  • 步骤:实现Runnable接口 → 重写run()方法 → 将实现类传入Thread构造器 → 调用start()启动;

  • 优势:可继承其他类,支持多线程共享同一个实现类实例(适合资源共享场景)。

  • 案例主方法线程逻辑与线程竞争

 // 1. 实现Runnable接口
 public class TestThread02 implements Runnable {
     // 2. 重写run方法
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
             System.out.println("子线程执行:" + i);
         }
     }
 ​
     public static void main(String[] args) {
         // 3. 创建Runnable实现类实例
         TestThread02 runnable = new TestThread02();
         
         // 4. 包装成Thread对象(Thread才是线程核心类)
         Thread thread = new Thread(runnable);
         // 5. 启动线程
         thread.start();
 ​
         // 4+5的简化写法:一步创建+启动
         // new Thread(runnable).start();
 ​
         // 主线程逻辑
         for (int i = 0; i < 5; i++) {
             System.out.println("主线程执行:" + i);
         }
     }
 }

3.实现Callable接口(有返回值 + 可抛异常)

  • 步骤:实现Callable<T>接口 → 重写call()方法(有返回值、可抛异常) → 通过线程池ExecutorService提交执行 → 获取返回值 → 关闭线程池;

  • 优势:可获取线程执行结果、可处理异常,适合需要 “线程执行后返回数据” 的场景(如多线程下载文件后校验结果)。

  • 案例:多线程执行返回值任务

 package src.com.xing;
 import java.util.concurrent.*;
 ​
 //call()无需手动调用,由线程池(ExecutorService)的工作线程自动调度执行触发开关是submit(Callable)方法,提交任务即触发后续的自动调用流程;手动调用 call () 会在当前线程同步执行,丧失多线程异步执行的特性,违背设计初衷;
 ​
 // 1. 实现Callable接口,指定返回值类型(示例为Boolean)
 public class TestThread03 implements Callable<Boolean> {
     private String taskName;
 ​
     // 构造器传参,标识任务
     public TestThread03(String taskName) {
         this.taskName = taskName;
     }
 ​
     // 2. 重写call方法(有返回值、可抛异常)
     @Override
     public Boolean call() throws Exception {
         System.out.println(taskName + "执行中...");
         Thread.sleep(1000); // 模拟任务耗时
         return true; // 任务执行成功返回true
     }
 ​
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         // 3. 创建Callable实例
         TestThread03 task1 = new TestThread03("线程任务1");
         TestThread03 task2 = new TestThread03("线程任务2");
 ​
         // 4. 创建线程池(固定3个线程)
         ExecutorService service = Executors.newFixedThreadPool(3);
 ​
         // 5. 提交任务,获取Future(用于接收返回值)
         Future<Boolean> result1 = service.submit(task1);
         Future<Boolean> result2 = service.submit(task2);
 ​
         // 6. 获取任务返回值(get()会阻塞,直到任务完成)
         boolean rs1 = result1.get();
         boolean rs2 = result2.get();
         System.out.println("任务1执行结果:" + rs1);
         System.out.println("任务2执行结果:" + rs2);
 ​
         // 7. 关闭线程池(必须关闭,否则程序不会结束)
         service.shutdown();
     }
 }
 ​
 ​

4.实际案例——多线程下载图片

需导入commons-io包(IDEA 可直接下载),用于简化 URL 文件下载:

  • 步骤 1:下载 commons-io jar 包

    1. 打开 Apache 官网下载地址:https://commons.apache.org/proper/commons-io/download_io.cgi

    2. 下滑找到「Binaries」区域,下载 commons-io-版本-bin.zip(版本在更新,Windows 注意后缀选bin. zip 包即可);

       

    3. 解压 zip 包,找到里面的 commons-io-2.15.1.jar 文件(仅仅需要第二个文件,核心文件,其余是开源文件测试文件...),复制到你项目的 lib 目录(若没有 lib 目录,手动在目根目录创建src\com\lib)。

    4. 右键点击「lib」文件夹!不是jar包,在弹出来的条目选择最下面的「添加为库...」完成导入

  • 主代码:

 import org.apache.commons.io.FileUtils;
 import java.io.File;
 import java.net.URL;
 ​
 // 1. 图片下载器工具类
 class WebDownloader {
     // 下载方法:参数1=图片URL,参数2=保存路径+文件名(必须带后缀,如.jpg/.png)
     public void download(String url, String savePath) {
         try {
             // 核心API:将URL内容下载为文件
             FileUtils.copyURLToFile(new URL(url), new File(savePath));
             System.out.println("图片下载完成:" + savePath);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
 ​
 // 2. 多线程下载实现类(实现Runnable)
 public class ImageDownloadThread implements Runnable {
     private String imgUrl; // 图片URL
     private String saveName; // 保存文件名(如:picture1.jpg)
 ​
     // 构造器传参
     public ImageDownloadThread(String imgUrl, String saveName) {
         this.imgUrl = imgUrl;
         this.saveName = saveName;
     }
 ​
     // 3. 重写run方法,执行下载
     @Override
     public void run() {
         WebDownloader downloader = new WebDownloader();
         // 注意:文件名必须带后缀(如.jpg),否则文件无法正常打开
         downloader.download(imgUrl, saveName);
     }
 ​
     // 主方法测试
     public static void main(String[] args) {
         // 示例图片URL(可替换为自己的图片地址)
         String url1 = "https://img-blog.csdnimg.cn/20240101000001.jpg";
         String url2 = "https://img-blog.csdnimg.cn/20240101000002.jpg";
 ​
         // 创建多线程任务
         Thread t1 = new Thread(new ImageDownloadThread(url1, "pic1.jpg"));
         Thread t2 = new Thread(new ImageDownloadThread(url2, "pic2.jpg"));
 ​
         // 启动线程(异步下载),可以看到图片1图片2下载顺序是随机的
         t1.start();
         t2.start();
     }
 }

5.实际案例——多人抢票

  • 主代码:

 package src.com.xing;
 ​
 ​
 //三个线程同时抢10张票
 public class TT0202 implements Runnable{
     private int ticketNums=10;//票数为10
     //1.重run方法
     @Override
     public void run() {
         while (true){
             if(ticketNums<=0){
                 break;
             }
             try {
                 Thread.sleep(200);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
             System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
         }
     }
 ​
     2.主方法
     public static void main(String[] args) {
         TT0202 tt0202=new TT0202();
         new Thread(tt0202,"大学生").start();
         new Thread(tt0202,"黄牛").start();
         new Thread(tt0202,"研究生").start();
     }
 }
 ​

 

 

二.静态代理模式(多线程底层核心原理)

  • 本质:真实对象和代理对象实现同一接口,代理对象封装真实对象,增强其功能;

  • 多线程底层:Thread就是代理类,Runnable实现类是真实对象,start()方法就是代理增强(启动线程 + 执行run())。

 //真实对象和代理对象都要实现同一个接口,代理对象要代理真实对象
 //真实角色
 // 1. 定义共同接口(婚姻接口)
 interface Marry {
     void happyMarry(); // 核心方法:结婚
 }
 ​
 // 2. 真实角色(你,要结婚的人)
 class You implements Marry {
     @Override
     public void happyMarry() {
         System.out.println("秦老师要结婚了,超开心!");
     }
 }
 ​
 // 3. 代理角色(婚庆公司,增强真实角色功能)
 class WeddingCompany implements Marry {
     private Marry target; // 代理的真实对象
 ​
     // 构造器传入真实对象
     public WeddingCompany(Marry target) {
         this.target = target;
     }
 ​
     // 核心方法:添加婚前、婚后服务
     @Override
     public void happyMarry() {
         beforeMarry(); // 婚前准备
         target.happyMarry(); // 执行真实对象的核心方法
         afterMarry(); // 婚后收尾
     }
 ​
     // 代理的方法
     private void beforeMarry() {
         System.out.println("婚庆公司布置现场,准备彩礼~");
     }
 ​
     private void afterMarry() {
         System.out.println("婚庆公司收尾款,打扫现场~");
     }
 }
 ​
 // 测试类
 public class StaticProxyDemo {
     public static void main(String[] args) {
         // 真实对象:你
         Marry you = new You();
         // 代理对象:婚庆公司,代理你结婚
         Marry proxy = new WeddingCompany(you);
         // 执行代理方法(增强后的结婚流程)
         proxy.happyMarry();
     }
 }

三.推导Lamda表达式

  • 1.前置概念:

在开始推导前,必须先明确一个核心前提:Lambda 表达式只能简化「函数式接口」的实现

所谓函数式接口,就是:

  • 接口中只有一个抽象方法(可以有默认方法、静态方法);

  • 可以用@FunctionalInterface注解标记(可选,但推荐,编译器会校验是否符合函数式接口规范)。

比如本文示例二中的Marry接口,就是一个典型的函数式接口:

 // 1. 定义共同接口(婚姻接口)
 interface Marry {
     void happyMarry(); // 核心方法:结婚
 }
  • 2.一步步推导 Lambda 表达式(从繁到简,按序号看)

 package src.com.xing;
 //推导lamda表达式
 ():对应接口抽象方法的参数列表(本例中 lamda () 无参数,所以是空括号);
 ->:Lambda 操作符,意为 “将参数传递给方法体”;
 {}:对应抽象方法的方法体
 ​
 /**总结1.只有函数式接口才能用lamda简化
  * 2.如果函数内代码多行,用{}代码块包裹
  * 3.多个参数可以去掉参数类型,但要在()内
  */
 public class TestLamda {
     public static void main(String[] args) {
         ILike like=new Like();
         like.lamda();
 ​
         like = new Like2();
         like.lamda();
 ​
 //        3..局部内部类(将实现类定义在方法(main 方法)内部,作用域仅限当前方法,临时使用更灵活,但依然需要写完整的类和重写方法。)
         class Like3 implements ILike {
             @Override
             public void lamda() {
                 System.out.println("i like lamda4");
             }
         }
         like=new Like3();
         like.lamda();
 ​
         //4.匿名内部类(去掉类的名称,直接通过 “接口 + 方法重写” 的方式创建对象,是简化的关键一步(Lambda 的前身))
         like =new ILike() {
             @Override
             public void lamda() {
                 System.out.println("i like lamda5");
             }
         };
         like.lamda();
 ​
         //5.用lamda简化(基于匿名内部类,去掉所有冗余结构,只保留 “方法参数” 和 “方法体”,这就是 Lambda 的核心!)
         like = () ->{
             System.out.println("i like lamda6");
         };
         like.lamda();
     }
 ​
     //2.静态内部类(将实现类定义为外部类的静态内部类,无需创建外部类对象即可使用,比独立实现类更紧凑,但仍需定义完整的类结构。)
     static class Like2 implements ILike{
         @Override
         public void lamda() {
             System.out.println("i like lamda3");
         }
     }
 ​
 }
 //定义一个函数接口
 interface ILike{
     void lamda();
 }
 ​
 //1.实现类(这是最基础的写法:定义一个独立的类实现接口,然后创建对象调用方法。)
 class Like implements ILike{
     @Override
     public void lamda() {
         System.out.println("i like lamda2");
     }
 }
 ​

 

四.线程状态修改

线程有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),核心操作需掌握以下 7 种:

1.线程停止(优雅停止,禁止用 stop/destroy)

 禁止使用 JDK 废弃方法(stop()/destroy()),会导致线程资源未释放;
 推荐方式: 次数控制自动停止; 标识位控制停止。
 package src.com.xing;
 ​
 public class TestStop implements Runnable{
     //1.设置一个标志位
     private boolean flag=true;
 ​
     public  void run(){
         int i =0;
         while (flag){
             System.out.println("run Thread"+i++);
 ​
         }
     }
 ​
     //重写stop方法,不是直接调用stop
     //设置一个公开方法停止线程,转换标志位
     public void stop(){
         this.flag=false;
     }
 ​
     public static void main(String[] args) {
         TestStop testStop=new TestStop();
         new Thread(testStop).start();
 ​
         for (int i = 0; i <=1000; i++) {
             System.out.println("main"+i);
             if(i==900){
                 testStop.stop();
                 System.out.println("线程停止了");
             }
         }
     }
 }

2.线程休眠(Thread.sleep ())

作用:让线程暂停指定时间,释放 CPU 资源,但不释放锁;

 `Thread.sleep(200);`

3.线程礼让(Thread.yield ())

作用:让当前线程放弃 CPU 执行权,回到就绪状态,与其他线程重新竞争 CPU;

注意:礼让不一定成功,取决于 CPU 调度(可能刚礼让又抢到执行权)。

 Thread.yield();

4.线程强制执行

作用:让调用join()的线程优先执行完毕,当前线程阻塞等待;

 thread.join();

5.获取线程状态(Thread.getState ())

 Thread.State state = thread.getState();
 System.out.println("线程状态:" + state);

6.线程优先级(1~10,默认 5)

作用:优先级越高,CPU 调度的概率越大(不是绝对优先);

 主线程默认5,其他范围1~10
 thread.setPriority(1);

7.守护线程(setDaemon (true))

作用:守护线程依赖用户线程一直执行,当所有用户线程结束,守护线程自动结束;

场景:垃圾回收(GC)、后台日志输出、监控线程等。

 package src.com.xing;
 public class TestDaemon {
     public static void main(String[] args) {
 ​
     Young young = new Young();
     God god = new God();
 ​
     Thread thread01 = new Thread(god);
     thread01.setDaemon(true);//守护线程需在start()前设置,否则抛出异常。
     thread01.start();
 ​
     new Thread(young).start();
     }
 }
 ​
 ​
 class God implements Runnable{
     @Override
     public void run() {
         while (true){
             System.out.println("GOD一直都在");
         }
     }
 }
 ​
 ​
 class Young implements Runnable{
     @Override
     public void run() {
         for (int i = 0; i < 365; i++) {
             System.out.println("每一天开心活着");
         }
         System.out.println("你死了");
     }
 }
 ​

五.线程同步机制(解决多线程资源竞争问题)

多线程共享资源时会出现 “线程不安全”(如卖票超卖、数据错乱),需通过同步机制保证原子性。

1.synchronized同步方法(隐式锁)

 法一、配合 `private` 封装数据,本质是为当前对象(this)加锁,保证同一时间只有一个线程能执行同步代码
 public synchronized void method(int args) {}
 ​
 法二、Obj 称为同步代码块,可以是任何对象,更灵活,推荐用共享资源作为监视器
 synchronized (Obj) {
     // 需要同步的代码
 }
 ​
 package src.com.synchronization;
 ​
 public class UnsafeBuyTicket {
     public static void main(String[] args) {
         BuyTickkkket buyTickkkket=new BuyTickkkket();
         new Thread(buyTickkkket,"大学生").start();
         new Thread(buyTickkkket,"黄牛").start();
         new Thread(buyTickkkket,"黄牛3").start();
     }
 }
 ​
 class BuyTickkkket implements Runnable{
     private int nums=100;
     boolean flag =true;
     @Override
     public void run() {
         //买票
         while (flag){
             try {
                 buy();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     }
     //添加同步后,不会出现卖到了第-1票的情况
     private synchronized void buy() throws InterruptedException {
         if(nums<=0){
             return;
         }
         Thread.sleep(100);
         System.out.println(Thread.currentThread().getName()+"买了"+nums--);
     }
 ​
 }

2.JUC安全集合

该集合自动实现了同步,保证共享资源的安全

 package src.com.synchronization;
 ​
 import java.util.concurrent.CopyOnWriteArrayList;
 ​
 //测试JUC自带安全类型的集合CopyOnWriteArrayList
 public class TestJUC {
     public static void main(String[] args) throws InterruptedException {
         CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>();
         for (int i = 0; i < 10000; i++) {
             new Thread(()->{
                 list.add(Thread.currentThread().getName());
             }).start();
         }
         Thread.sleep(1000);
         System.out.println(list.size());
     }
 }

3.死锁

  • 核心原因

    • 线程 1 持有锁 A,等待锁 B;

    • 线程 2 持有锁 B,等待锁 A;

    • 双方互相等待,无法释放锁,程序卡死。

  • 代码示例(死锁场景)

 package src.com.synchronization;
 //死锁
 public class DeadLock {
     public static void main(String[] args) {
         Makeup makeup01=new Makeup(0,"girl01");
         Makeup makeup02=new Makeup(1,"girl02");
         makeup02.start();
         makeup01.start();
     }
 }
 ​
 class Lipstick{ }
 class Mirror{}
 class Makeup extends Thread{
     //用static保证资源只有一份
     static Mirror mirror=new Mirror();
     static Lipstick lipstick=new Lipstick();
     int choice;
     String name;//人
 ​
     public Makeup(int choice, String name) {
         this.choice = choice;
         this.name = name;
     }
 ​
     @Override
     public void run() {
         try {
             makeup();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
 ​
     }
 ​
     //化妆,互相持有对方的锁
     private void makeup() throws InterruptedException {
         if(choice==0){
             synchronized (mirror){//获得镜子的锁
                 System.out.println(this.name+"镜子");
                 Thread.sleep(1000);
 ​
                 synchronized (lipstick){
                     System.out.println(this.name+"口红");
                 }
             }
         }else {
             synchronized (lipstick){//获得镜子的锁
                 System.out.println(this.name+"口红");
                 Thread.sleep(1000);
 ​
                 synchronized (mirror){
                     System.out.println("镜子");
                 }
             }
 ​
         }
 ​
     }
 }

4.Lock锁(显式锁,比 synchronized 更灵活)

  • java.util.concurrent.locks.Lock是接口,常用实现类ReentrantLock

  • 优势:可手动加锁 / 解锁、支持超时锁、可中断锁、公平锁(按线程等待顺序获取锁);

  • 规范:必须在try块中加锁,finally块中解锁(确保锁一定释放)。

 try {
     lock.lock();//加锁
     {代码块}
 }finally {
     lock.unlock();//解锁

5.管程法

  • 核心 API

  • wait():线程等待,释放锁,进入等待队列;

  • wait(long timeout):超时等待,超时后自动唤醒;

  • notify():随机唤醒一个等待的线程;

  • notifyAll():唤醒所有等待的线程

 package src.com.synchronization;
 ​
 //测试生产者消费者模型--->利用缓冲区管程法
 //生产者,消费者,产品,核心思路如下
 //生产者:生产产品→通知消费者→若仓库满则等待;
 //消费者:消费产品→通知生产者→若仓库空则等待。
 public class TestPC {
     public static void main(String[] args) {
         SynContainer container=new SynContainer();
         new Consumer(container).start();
         new Productor(container).start();
 ​
     }
 }
 class Productor extends Thread{
     SynContainer container;
 ​
     public Productor(SynContainer container) {
         this.container = container;
     }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             container.push(new Chicken(i));
             System.out.println("生产了第"+i+"只鸡");
         }
     }
 }
 ​
 ​
 class Consumer extends Thread{
     SynContainer container;
     public Consumer(SynContainer container) {
         this.container = container;
     }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             System.out.println("消费了第"+container.pop().id+"只鸡");
         }
     }
 }
 class  Chicken{
     int id;
 ​
     public Chicken(int id) {
         this.id = id;
     }
 }
 //缓冲区
 class SynContainer{
     //需要一个容器大小
     Chicken[] chickens=new Chicken[10];
     int count=0;
 ​
     //生产者放入产品
     public synchronized void push(Chicken chicken){
         //
         if(count==chickens.length){
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
         chickens[count++]=chicken;
         this.notify();
 ​
     }
 ​
     //消费者消费产品
     public synchronized Chicken pop(){
         if(count==0){
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
         Chicken chicken=chickens[--count];
         this.notify();
         return chicken;
 ​
     }
 ​
 }

6.信号灯法(标志位法,简化版生产者消费者)

 //生产和消费必须严格交替执行的场景(如流水线作业、消息队列的顺序消费)
 package src.com.synchronization;
 //测试生产者消费者模型--->利用信号灯法
 //演员,观众,节目
 public class TestPc2 {
     public static void main(String[] args) {
         TV tv=new TV();
         new Player(tv).start();
         new Watcher(tv).start();
     }
 ​
 ​
 }
 ​
 class Player extends Thread{
     TV tv;
 ​
     public Player(TV tv) {
         this.tv = tv;
     }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             if(i%2==0){
                 this.tv.play("节目1");
             }else {
                 this.tv.play("节目2");
             }
         }
     }
 }
 ​
 class Watcher extends Thread{
     TV tv;
 ​
     public Watcher(TV tv) {
         this.tv = tv;
     }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             this.tv.watch();
         }
     }
 }
 ​
 class TV{
     //演员表演,观众等待 T
 ​
     String voive;
     boolean flag=true;
     //观众观看,演员等待  F
     public synchronized void play(String voive){
         if(!flag){
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
         System.out.println("演员表演了"+voive);
         this.notifyAll();
         this.voive=voive;
         this.flag=!this.flag;
     }
 ​
     public synchronized void watch(){
         if(flag){
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
         System.out.println("观众观看了"+voive);
         //通知演员表演
         this.notify();
         this.flag=!this.flag;
     }
 }
 ​

7.线程池(复用线程,减少创建销毁开销)

频繁创建 / 销毁线程会消耗大量资源,线程池可复用线程,降低资源消耗、提高响应速度、便于管理线程

 package src.com.synchronization;
 ​
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 ​
 public class TestPool {
     public static void main(String[] args) {
         //1.创建线程池,参数为线程池大小
         ExecutorService service= Executors.newFixedThreadPool(10);
 ​
         service.execute(new MyTheard());
         service.execute(new MyTheard());
         service.execute(new MyTheard());
         service.execute(new MyTheard());
 ​
         //关闭连接
         service.shutdown();
     }
 }
 class MyTheard implements Runnable{
     @Override
     public void run() {
 ​
             System.out.println(Thread.currentThread().getName());
 ​
     }
 }

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐