并发多线程

文章目录

  • [1. 线程的制作方法](#1. 线程的制作方法)
    • [1.1 继承Thread类创建线程](#1.1 继承Thread类创建线程)
    • 1.2实现Runnable接口创建线程
    • [1.3 通过Callable和FutureTask创建线程](#1.3 通过Callable和FutureTask创建线程)
    • [1.4 通过线程池创建线程](#1.4 通过线程池创建线程)
  • [2. 线程的生命周期](#2. 线程的生命周期)
    • [2.1 线程的六种状态](#2.1 线程的六种状态)
    • [2.2 WAITING和TIMED_WAITING的区别](#2.2 WAITING和TIMED_WAITING的区别)
  • [3. 线程核心方法区别](#3. 线程核心方法区别)
    • [3.1 sleep和wait区别](#3.1 sleep和wait区别)
    • [3.2 run()方法和start()方法的区别](#3.2 run()方法和start()方法的区别)
    • [3.3 notify()和notifyAll()的区别](#3.3 notify()和notifyAll()的区别)
  • [4. 线程编排技术](#4. 线程编排技术)
  • [5. 线程顺序执行的实现方法](#5. 线程顺序执行的实现方法)
    • [5.1 使用join方法](#5.1 使用join方法)
    • [5.2 使用CountDownLatch](#5.2 使用CountDownLatch)
    • [5.3 使用CyclicBarrier](#5.3 使用CyclicBarrier)
    • [5.4 使用Semaphore](#5.4 使用Semaphore)
    • [5.5 使用线程池](#5.5 使用线程池)
    • [5.6 使用CompletableFuture](#5.6 使用CompletableFuture)

1. 线程的制作方法

在Java种,共有4种方法创建线程

1.1 继承Thread类创建线程

java 复制代码
class TaskTread extends  Thread {
    private String taskName;

    public TaskTread(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println("执行任务" + taskName);
        try {
            Thread.sleep(100);
            System.out.println("正在执行任务" + taskName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成任务" + taskName);
    }
}
    public class  ThreadExample{
        public static void main(String[] args) {
            TaskTread task = new TaskTread("taskTread");
            task.start();
        }
    }

1.2实现Runnable接口创建线程

java 复制代码
class TaskRunnable implements  Runnable {
  private String taskName;

  public TaskRunnable(String taskName) {
      this.taskName = taskName;
  }

  @Override
  public void run() {
      System.out.println("执行任务" + taskName);
      try {
          Thread.sleep(100);
          System.out.println("正在执行任务" + taskName);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println("完成任务" + taskName);
  }
}
  public class  RunnableExample {
      public static void main(String[] args) {
          Thread thread=new Thread(new TaskRunnable("task1"));
          thread.start();
      }
  }

1.3 通过Callable和FutureTask创建线程

Callable 的优势在于:

  • 有返回值:可以指定返回数据的类型(泛型)。
  • 能抛出异常:允许直接抛出受检异常。

由于 Thread 类的构造函数只接受 Runnable,不直接接受 Callable,所以我们需要 FutureTask 作为中介

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable = () -> {
            System.out.println("正在执行Callable任务...");
            Thread.sleep(3000);
            return "Callable任务执行完成!";
        };
        FutureTask futureTask = new FutureTask(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("主线程继续执行...");
        System.out.println("Callable任务的结果: " + futureTask.get());

    }
}
  • Future 接口:异步结果的管理者

    Future 并不是结果本身,它是一个协议,提供了检查任务状态和获取结果的方法。

  • FutureTask 是 Future 接口的一个具体实现类。

    特性:它同时实现了 Runnable 和 Future。这意味着它既能被 Thread 执行,又能作为提货单存结果。

1.4 通过线程池创建线程

java 复制代码
import java.util.concurrent.*;

public class ThreadPoolExample{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Callable callable=()->{
            System.out.println("线程池执行任务");
            Thread.sleep(3000);
            return "任务完成";
        };
        System.out.println("提交任务");
        Future future=executorService.submit(callable);
        System.out.println("执行其他任务");
        System.out.println("获取结果"+future.get());
        executorService.shutdown();
    }

}
复制代码
四种创造线程的方式归根结底都是基于继承Thread 类和实现Runnable来实现的。

2. 线程的生命周期

2.1 线程的六种状态

  • NEW(新建):新建了一个线程对象- 在new Thread() 之后,调用 start() 之前。
  • RUNNABLE(可运行):正在运行或等待 CPU 分配时间片-在调用了 start()。在 Java 中,就绪和运行统称为 RUNNABLE。
  • BLOCKED(阻塞):等待获取监视器锁-试图进入 synchronized 代码块但锁被别人拿着。
  • WATING(等待):无限期等待另一个线程执行特定操作-调用了 wait()、join() 或 LockSupport.park()。
  • TIMED_WAITING(计时等待):在指定时间内等待超过后可自行返回-调用了 sleep(ms)、wait(ms) 或 join(ms)。
  • TERMINATED (终止): 线程已经执行完毕-run() 方法执行结束,或者由于异常退出了。

2.2 WAITING和TIMED_WAITING的区别

  • WAITING (无限期等待)

    线程进入这个状态后,它会释放 CPU 资源。它在等待一个特定的信号。

    进入方式:调用 object.wait()。

    退出方式:只有另一个线程执行了同一个对象的 object.notify() 或 object.notifyAll(),这个线程才有机会从等待队列转移到同步队列,去竞争锁,最后回到 RUNNABLE 状态。

    风险:如果没人通知,该线程会产生死锁或导致内存泄漏。

  • TIMED_WAITING (计时等待)

    这是一种更"安全"的等待方式,因为它给等待设置了底线。

    进入方式:调用 Thread.sleep(3000) 或 object.wait(3000)。

    退出方式:

    时间到:3 秒钟一过,操作系统自动把它唤醒。

    被提前叫醒:在 3 秒内,如果有人调了 notify(),它也会醒。

    应用场景:网络请求超时控制、定期任务执行。

3. 线程核心方法区别

3.1 sleep和wait区别

  • 所属类不同:

    sleep 是 Thread 类的静态方法。

    wait 是 Object 类的方法(意味着任何 Java 对象都有这个功能)。

  • 对"锁"的处理不同:

    sleep 很自私:它在 TIMED_WAITING 期间,不会释放锁。它抱着锁睡觉,别人进不来。

    wait 很无私:它在进入 WAITING 时,会释放自己拿的锁。它把位置让出来,让别的线程先干活,自己去旁边等着。

  • 使用环境不同:

    wait 必须放在 synchronized 代码块里,否则会报 IllegalMonitorStateException。

    sleep 随处可用。

3.2 run()方法和start()方法的区别

start()方法是启动线程的入口,而run()方法是在当前主线程执行。

java 复制代码
public  class startVsRun{
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("当前线程: " + Thread.currentThread().getName());
        });

        t1.start();

        t1.run();

    }
}

3.3 notify()和notifyAll()的区别

  • 唤醒范围不同:
    notify():只唤醒一个处于wait状态的线程
    notifyAll():唤醒所有处于wait状态的线程
  • 优缺点
    notify()唤醒一个线程可能出现该线程无法工作的风险
    notifyAll()唤醒所有线程降低风险
    当调用 notifyAll() 时,所有线程都会被唤醒去抢同一把锁。但最终只有一个线程能抢到,其他没抢到的线程又要重新回到阻塞状态。这会带来瞬间的 CPU 压力。
java 复制代码
public  class NotifyExample {
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <=3 ; i++) {
            final int taskId = i;
            new Thread(()->{
                synchronized (lock) {
                    try{
                        System.out.println("线程"+taskId+"正在等待通知...");
                        lock.wait();
                        System.out.println("线程"+taskId+"收到通知,继续执行...");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        Thread.sleep(3000);
        synchronized (lock) {
            System.out.println("notify()方法被调用,通知一个等待的线程...");
            lock.notify();
        }
        Thread.sleep(3000);
        synchronized (lock) {
            System.out.println("notifyAll()方法被调用,通知所有等待的线程...");
            lock.notifyAll();
        }

    }

}

4. 线程编排技术

在 FutureTask 例子中,有一个很尴尬的问题:主线程必须调用 get() 并在那里阻塞死等。

CompletableFuture 引入了** 回调(Callback)**机制

  • 不用等:任务做完了,它会自动触发下一步,主线程可以去忙别的。

  • 链式操作:支持"流水线"作业(先干 A,A 完了自动干 B,B 完了干 C)。

  • 异常处理:可以像 try-catch 一样优雅地处理异步过程中的报错。

java 复制代码
import java.util.concurrent.CompletableFuture;

public class CFDemo {
    public static void main(String[] args) throws Exception {
        // 1. 提交一个异步任务(洗菜)
        CompletableFuture<String> cookPlan = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":正在洗菜...");
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            return "干净的蔬菜";
        });

        // 2. 链式处理:洗完菜后自动"炒菜"(thenApply 相当于加工)
        CompletableFuture<String> result = cookPlan.thenApply(vegetable -> {
            System.out.println(Thread.currentThread().getName() + ":正在炒" + vegetable);
            return "热腾腾的炒菜";
        });

        // 3. 最终回调:菜好了自动"摆盘"(thenAccept 消费结果)
        result.thenAccept(dish -> {
            System.out.println(Thread.currentThread().getName() + ":大功告成,把 " + dish + " 端上桌!");
        });

        System.out.println("主线程:菜好了喊我...");

        // 防止主线程立刻结束
        Thread.sleep(5000);
    }
}

5. 线程顺序执行的实现方法

5.1 使用join方法

join() 方法的作用是让当前线程等待目标线程执行完成后再继续执行。这就像是在接力比赛中,下一个选手必须等到上一个选手把棒子传到手里。

java 复制代码
 Thread t1 = new Thread(() -> System.out.println("T1 执行"));
        Thread t2 = new Thread(() -> {
            try {
                System.out.println("T2 等待 T1 结束");
                t1.join(); // 等待 t1 结束
                System.out.println("T2 继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T2 执行");
        });
        t2.start();
        t1.start();

5.2 使用CountDownLatch

核心思想: "一个(或多个)线程等待其他线程完成某件事。" 就像是旅游团的导游,必须在点名确认所有游客(其他线程)都上了大巴之后,才能通知司机开车(主线程继续往下走)。

CountDownLatch 维护了一个内部计数器,主要通过两个核心方法来控制流程:

  • countDown():计数器减 1(通常由"前置任务"线程调用)。
  • await():阻塞当前线程,直到计数器变为 0(通常由"主控"或"后续任务"线程调用)。
java 复制代码
import java.util.concurrent.CountDownLatch;

public class LatchDemo {
   public static void main(String[] args) throws InterruptedException {
       // 1. 初始化计数器为 3
       CountDownLatch latch = new CountDownLatch(3);

       for (int i = 1; i <= 3; i++) {
           final int id = i;
           new Thread(() -> {
               try {
                   System.out.println("子任务 " + id + " 正在执行...");
                   Thread.sleep(1000); // 模拟耗时操作
                   System.out.println("子任务 " + id + " 完成!");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               } finally {
                   // 2. 关键:每个线程完成后,计数器减 1
                   latch.countDown();
               }
           }).start();
       }

       System.out.println("主线程等待子任务完成...");
       // 3. 关键:主线程在这里阻塞,直到计数器归零
       latch.await();

       System.out.println("--- 所有子任务已就绪,主程序正式启动! ---");
   }
}

5.3 使用CyclicBarrier

核心思想: "一组线程互相等待,直到所有人都到达同一个进度节点,大家再一起往下走。" 就像是几个同学约好去图书馆自习,规定必须等所有人都在图书馆门口集合完毕,才能一起进去。先到的人只能在门口等后到的人。

java 复制代码
import java.util.concurrent.*;

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        // 定义参与的线程数量(同学的数量)
        int numberOfFriends = 3;

        // 1. 初始化 CyclicBarrier
        // 参数1:parties,表示需要等待的线程数量
        // 参数2:barrierAction,当所有线程都到达栅栏时,优先执行的汇总任务
        CyclicBarrier barrier = new CyclicBarrier(numberOfFriends, () -> {
            System.out.println("3位同学都已到达图书馆门口!大家可以一起进去了!\n");
        });

        // 创建一个线程池来模拟同学们
        ExecutorService executor = Executors.newFixedThreadPool(numberOfFriends);

        System.out.println("--- 约定好今天去图书馆自习 ---");

        // 派发 3 个同学任务
        for (int i = 1; i <= numberOfFriends; i++) {
            final int friendId = i;

            executor.submit(() -> {
                try {
                    System.out.println("同学 " + friendId + " 正在路上...");
                    // 模拟每个同学路上的耗时不同 (0到2秒之间)
                    Thread.sleep((long) (Math.random() * 2000));

                    System.out.println("-> 同学 " + friendId + " 到达图书馆门口,开始等待其他人。");

                    // 2. 关键点:调用 await()
                    // 线程运行到这里会被阻塞,直到 3 个线程都调用了 await()
                    barrier.await();

                    // 3. 跨过栅栏后继续执行的代码
                    System.out.println("同学 " + friendId + " 走进图书馆,开始学习!");

                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

5.4 使用Semaphore

Semaphore可以控制同时访问资源的线程数量

java 复制代码
public class SemaphoreDemo {

    public static void main(String[] args) {
        // 1. 初始化 Semaphore,发放 1个"停车许可证"
        Semaphore semaphore = new Semaphore(1);

        // 模拟 3 辆汽车同时来停车场,每辆车需要一个许可证才能进入
        int carCount = 3;
        ExecutorService executor = Executors.newFixedThreadPool(carCount);

        for (int i = 1; i <= carCount; i++) {
            final int carId = i;
            executor.submit(() -> {
                try {
                    System.out.println(" 汽车 " + carId + " 来到停车场门口。");

                    // 2. 尝试获取许可证 (如果没有空位,线程会在这里阻塞等待)
                    semaphore.acquire();

                    System.out.println(" 汽车 " + carId + " 拿到停车卡,驶入停车位!");

                    // 模拟停车时长 (1到3秒随机)
                    Thread.sleep((long) (Math.random() * 2000 + 1000));

                    System.out.println(" 汽车 " + carId + " 离开停车场。");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 3. 归还许可证 (关键!必须在 finally 里释放,防止异常导致死锁)
                    semaphore.release();
                }
            });
        }

        executor.shutdown();
    }
}

5.5 使用线程池

线程池内部使用队列存储任务,任务按照提交顺序执行。创建只有一个线程的线程池可以保证任务按照顺序执行。

java 复制代码
public class SingleThreadOrderDemo {
    public static void main(String[] args) {
        // 创建一个单线程的线程池
        ExecutorService singleExecutor = Executors.newSingleThreadExecutor();

        System.out.println("--- 按照 1, 2, 3 的顺序提交任务 ---");
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            singleExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 正在执行任务: " + taskId);
                try {
                    Thread.sleep(1000); // 模拟耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        singleExecutor.shutdown();
    }
}

5.6 使用CompletableFuture

模拟场景:用户画像聚合查询

我们需要并发获取以下四个数据:

User Info(基本信息)

Order Count(订单数量)

Member Points(会员积分)

Latest Log(最近登录日志)

java 复制代码
public class MultiTaskDemo {
    public static void main(String[] args) {
        // 1. 自定义线程池
        ExecutorService executor = Executors.newFixedThreadPool(4);

        long start = System.currentTimeMillis();

        // 2. 开启 4 个并发任务
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            simulateDelay(500); // 模拟查询基本信息
            return "用户:张三";
        }, executor);

        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            simulateDelay(300); // 模拟查询订单数
            return 15;
        }, executor);

        CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> {
            simulateDelay(600); // 模拟查询积分
            return 2000;
        }, executor);

        CompletableFuture<String> task4 = CompletableFuture.supplyAsync(() -> {
            simulateDelay(400); // 模拟查询日志
            return "2026-03-16 登录成功";
        }, executor);

        // 3. 使用 allOf 等待所有任务完成
        // allOf 的作用是:只有括号里所有任务都结束了,它才会触发后面的 thenRun。
        CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3, task4);

        // 4. 当所有任务完成后,聚合结果
        allTasks.thenRun(() -> {
            try {
                // 此时用 join() 或 get() 是不会阻塞的,因为任务已经全完了
                String userInfo = task1.join();
                int orders = task2.join();
                int points = task3.join();
                String log = task4.join();

                System.out.println("---- 聚合结果 ----");
                System.out.println(userInfo + " | 订单数:" + orders + " | 积分:" + points + " | 日志:" + log);
                System.out.println("总耗时: " + (System.currentTimeMillis() - start) + " ms");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).join(); // 这里 join 是为了让主线程等回调执行完

        executor.shutdown();
    }

    private static void simulateDelay(int ms) {
        try { TimeUnit.MILLISECONDS.sleep(ms); } catch (InterruptedException e) {}
    }
}

相关推荐
、花无将2 小时前
安装:apache-tomcat
java·tomcat·apache
gaoshan123456789102 小时前
springboot 使用zip4j下载压缩包,压缩包内的数据来自oss文件管理服务器
java·服务器·spring boot
一晌小贪欢2 小时前
Python魔法:列表与字典推导式深度解析
开发语言·windows·python·列表推导式·python列表·python字典·字典推导式
ZZhYasuo2 小时前
冒泡排序1
java·算法·排序算法
独断万古他化2 小时前
【抽奖系统开发实战】Spring Boot 项目的奖品模块开发:文件上传、时序设计与奖品创建
java·spring boot·后端·mvc·文件
东离与糖宝2 小时前
告别AI投毒!Java后端实现大模型prompt过滤与敏感信息拦截实战
java·人工智能
yashuk2 小时前
怎么下载安装yarn
java
23.2 小时前
【Java】Arrays工具类——数组操作终极指南
java·算法·面试
ok_hahaha2 小时前
java从头开始-苍穹外卖-day12-数据统计以及excel报表
java