并发编程场景题学习

预备知识、各种同步工具

1、synchronized + wait/notify

概括:Java内置的最基础的线程同步机制,基于对象监视器实现。

用途:用于简单的线程互斥和等待通知机制,如传统的生产者-消费者问题。

2、CountDownLatch

概括:一次性的事件等待器,通过倒计时控制线程执行顺序。

用途:等待多个并行任务全部完成后再继续执行,如初始化完成后启动主应用。

3、CompletableFuture

概括:声明式的异步编程框架,支持任务链式编排和组合。

用途:优雅地编排多个异步任务,处理复杂的异步依赖关系,如并行调用多个微服务。

4、Semaphore

概括:基于许可证的资源控制器,限制并发访问数量。

用途:控制对有限资源的并发访问,如数据库连接池、限流器。

5、ReentrantLock + Condition

概括:功能更丰富的显式锁,提供比synchronized更灵活的控制。

用途:需要高级同步特性的场景,如可中断锁、公平锁、多个等待条件。

6、BlockingQueue

BlockingQueue 是 Java 并发包中开箱即用的 "生产者 - 消费者" 解决方案,本质是对 ReentrantLock + Condition 的高级封装(比如 ArrayBlockingQueue 底层就是用这组工具实现的)

一、线程协作与同步

1、按序打印(Leetcode 1114)

给你一个类:

java 复制代码
public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

线程 A 将会调用 first() 方法

线程 B 将会调用 second() 方法

线程 C 将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

java 复制代码
class Foo {
    public Foo() {}
    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
    }
    public void second(Runnable printSecond) throws InterruptedException {
        printSecond.run();
    }
    public void third(Runnable printThird) throws InterruptedException {
        printThird.run();
    }
}

方法一:忙等待

优点:简单

缺点:空转CPU,可能会死循环

java 复制代码
class Foo {
    private volatile int x=0;
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        x++;
    }
    public void second(Runnable printSecond) throws InterruptedException {
        while(x!=1){}
        printSecond.run();
        x++;
    }
    public void third(Runnable printThird) throws InterruptedException {
        while(x!=2){}
        printThird.run();
    }
}

1)这里x++因为不是原子性操作,如果有多个线程同时调用first/second/third 会出现问题,

2)while循环里面可以加Thread.yield() 减少自旋的 CPU 消耗

java 复制代码
class Foo {
    private AtomicInteger x=new AtomicInteger(0);
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        x.incrementAndGet();
    }
    public void second(Runnable printSecond) throws InterruptedException {
        while(x.get()!=1){Thread.yield();}
        printSecond.run();
        x.incrementAndGet();
    }
    public void third(Runnable printThird) throws InterruptedException {
        while(x.get()!=2){Thread.yield();}
        printThird.run();
    }
}

方法二:synchronized + wait/notify

java 复制代码
class Foo {
    private int x=0;
    private final Object lock=new Object();
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
        synchronized(lock){
            printFirst.run();
            x++;
            lock.notifyAll();
        }
    }
    public void second(Runnable printSecond) throws InterruptedException {
        synchronized(lock){
            while(x!=1){lock.wait();}
            printSecond.run();
            x++;
            lock.notifyAll();
        }
    }
    public void third(Runnable printThird) throws InterruptedException {
        synchronized(lock){
            while(x!=2){lock.wait();}
            printThird.run();
            lock.notifyAll();
        }
    }
}

方法三:CountDownLatch

java 复制代码
class Foo {
    private final CountDownLatch latch1 = new CountDownLatch(1);
    private final CountDownLatch latch2 = new CountDownLatch(1);
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
            printFirst.run();
            latch1.countDown();
    }
    public void second(Runnable printSecond) throws InterruptedException {
            latch1.await();
            printSecond.run();
            latch2.countDown();
        
    }
    public void third(Runnable printThird) throws InterruptedException {
            latch2.await();
            printThird.run();  
    }
}

方法四:Semaphore

java 复制代码
class Foo {
    private final Semaphore sem2 = new Semaphore(0);
    private final Semaphore sem3 = new Semaphore(0);
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
            printFirst.run();
            sem2.release();
    }
    public void second(Runnable printSecond) throws InterruptedException {
            sem2.acquire();
            printSecond.run();
            sem3.release();
    }
    public void third(Runnable printThird) throws InterruptedException {
             sem3.acquire();
            printThird.run(); 
    }
}

方法五:ReentrantLock+Condition

java 复制代码
class Foo {
    private final Lock lock = new ReentrantLock();
    private final Condition cond1 = lock.newCondition();
    private final Condition cond2 = lock.newCondition();
    private int flag = 0;
    public Foo() { }
    public void first(Runnable printFirst) throws InterruptedException {
            lock.lock();
            try{
                 printFirst.run();
                 flag++;
                 cond1.signal();
            }finally{
                lock.unlock();
            }
    }
    public void second(Runnable printSecond) throws InterruptedException {
            lock.lock();
            try{
                while (flag != 1) {
                cond1.await();
                }
                printSecond.run();
                flag++;
                cond2.signal();
            }finally{
                lock.unlock();
            }
    }
    public void third(Runnable printThird) throws InterruptedException {
            lock.lock();
            try{
                while (flag != 2) {
                cond2.await();
                }
                printThird.run();
            }finally{
                lock.unlock();
            }
    }
}

方法六:CompletableFuture

java 复制代码
class Foo {
    private CompletableFuture<Void> cf1 = new CompletableFuture<>();
    private CompletableFuture<Void> cf2 = new CompletableFuture<>();
    public void first(Runnable printFirst) {
        printFirst.run();
        cf1.complete(null);  // 信号:first完成了
    }
    public void second(Runnable printSecond) {
        cf1.join();  // 阻塞等待cf1完成
        printSecond.run();
        cf2.complete(null);  // 信号:second完成了
    }
    public void third(Runnable printThird)  {
        cf2.join();  // 阻塞等待cf2完成
        printThird.run();
    }
}

2、交替打印(Leetcode 1115)

给你一个类:

java 复制代码
class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }

  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程将会共用一个 FooBar 实例:

线程 A 将会调用 foo() 方法,而

线程 B 将会调用 bar() 方法

请设计修改程序,以确保 "foobar" 被输出 n 次。
方法一:忙等待

java 复制代码
class FooBar {
    private int n;
    public FooBar(int n) {
        this.n = n;
    }
    private volatile boolean state = true;
    public void foo(Runnable printFoo) throws InterruptedException {     
        for (int i = 0; i < n; ) {
            if(state) {
        	printFoo.run();
            	i++;
            	state = false;
            }else{
                Thread.yield();
            }
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {       
        for (int i = 0; i < n; ) {
            if(!state) {
        	printBar.run();
        	i++;
        	state = true;
            }else{
                Thread.yield();
            }
        }
    }
}

方法二:synchronized + wait/notify

java 复制代码
class FooBar {
    private int n;
    public FooBar(int n) {
        this.n = n;
    }
    private  boolean state = true;
    private final Object lock=new Object();
    public void foo(Runnable printFoo) throws InterruptedException {     
        for (int i = 0; i < n; i++) {
            synchronized(lock){
                while(!state){
                    lock.wait();
                }
                printFoo.run();
                state=false;
                lock.notifyAll();
            }
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {       
        for (int i = 0; i < n;i++) {
            synchronized(lock){
                while(state){
                    lock.wait();
                }
                printBar.run();
                state=true;
                lock.notifyAll();
            }
        }
    }
}

方法三:Semaphore

java 复制代码
class FooBar {
    private int n;
    public FooBar(int n) {
        this.n = n;
    }
    private final Semaphore sem1 = new Semaphore(1);
    private final Semaphore sem2 = new Semaphore(0);
    public void foo(Runnable printFoo) throws InterruptedException {     
        for (int i = 0; i < n; i++) {
            sem1.acquire();
        	printFoo.run();
            sem2.release();
            
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {       
        for (int i = 0; i < n; i++) {
            sem2.acquire();
        	printBar.run();
            sem1.release();
        }
    }
}

方法四:ReentrantLock+Condition

java 复制代码
class FooBar {
    private int n;
    public FooBar(int n) {
        this.n = n;
    }
    private final Lock lock = new ReentrantLock();
    private final Condition cond = lock.newCondition();
    private boolean flag = true;

    public void foo(Runnable printFoo) throws InterruptedException {     
        for (int i = 0; i < n; i++) {
            lock.lock();
            try{
                while(!flag){
                cond.await();
                 }
        	    printFoo.run();
                flag=false;
                cond.signal(); 
            }finally{
                lock.unlock();
            }
           
            
        }
    }
    public void bar(Runnable printBar) throws InterruptedException {       
        for (int i = 0; i < n; i++) {
            lock.lock();
            try{
                while(flag){
                cond.await();
                 }
        	    printBar.run();
                flag=true;
                cond.signal(); 
            }finally{
                lock.unlock();
            }
        }
    }
}

3、打印零与奇偶数(Leetcode 1116)

4、H2O生成(Leetcode 1117)

5、交替打印字符串(Leetcode 1195)

6、哲学家进餐(Leetcode 1226)

二、线程超时控制

1、启动一个线程完成某项任务比较耗时,对这个线程设置一个超时时间是2分钟,在2分钟时无论是否完成都需要退出它,如果提前完成就提前退出,如何实现

java 复制代码
public class TimeoutThreadExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(()->{
            try {
                Thread.sleep(3000);
                return "任务完成";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();   //不是题目要求,而是健壮性
                return "任务中断";
            }
        });

        try {
            String s=future.get(2,TimeUnit.SECONDS);
            System.out.println(s);
        } catch (TimeoutException e) {
            boolean cancelled = future.cancel(true);
            System.out.println("取消" + (cancelled ? "成功" : "失败"));

        }finally {
            executor.shutdown();
        }
    }
}

三、分治并行

1、有一些内容需要一百万次For循环,然后需要你使用线程池,并发执行去降低他的耗时,最后把结果汇总。这个思路是什么

java 复制代码
public class TimeoutThreadExample {
    public static void main(String[] args) throws Exception {
        int total = 1_000_000;
        int threadCount = 8;

        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        int batchSize = total / threadCount;
        List<Future<Integer>> futures = new ArrayList<>();
        for (int i = 0; i < threadCount; i++) {
            int start = i * batchSize;
            int end = start+batchSize;
            futures.add(executor.submit(() -> {
                int sum=0;
                for (int j = start; j < end; j++) {
                    sum += func();
                }
                return sum;
            }));
        }
        int result = 0;
        for (Future<Integer> future : futures) {
            result += future.get();  // 阻塞等待每个结果
        }
        System.out.println("最终结果: " + result);
        // 6. 关闭线程池
        executor.shutdown();
    }
    public static int func(){
        return 2;
    }
}

四、多任务竞速

1、现在有三个访问天气预报的接口,他们访问的速度不同,想最快返回天气预报,怎么做

java 复制代码
public class WeatherRaceBest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 方法引用正常使用,无异常报错
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi1, executor);
        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi2, executor);
        CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(WeatherRaceBest::getWeatherFromApi3, executor);

        // 获取首个完成的结果
        CompletableFuture<Object> fastestCf = CompletableFuture.anyOf(cf1, cf2, cf3);
        String fastestResult = (String) fastestCf.get();

        // 取消未完成的任务
        if (!cf1.isDone()) cf1.cancel(true);
        if (!cf2.isDone()) cf2.cancel(true);
        if (!cf3.isDone()) cf3.cancel(true);

        System.out.println("最快的API结果:" + fastestResult);
        executor.shutdown(); // 关闭线程池
    }

    private static String getWeatherFromApi1() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 包装为运行时异常,无需声明throws
            throw new RuntimeException("API1调用被中断", e);
        }
        return "【API1】北京:25℃ 晴";
    }

    private static String getWeatherFromApi2() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException("API2调用被中断", e);
        }
        return "【API2】北京:25℃ 晴";
    }

    private static String getWeatherFromApi3() {
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            throw new RuntimeException("API3调用被中断", e);
        }
        return "【API3】北京:25℃ 晴";
    }
}

六、设计模式

1、生产者-消费者模式

java 复制代码
public class TimeoutThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个有界阻塞队列,容量为 3
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        // 生产者线程
        new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    String msg = "消息-" + i;
                    queue.put(msg); // 自动阻塞(如果队列满)
                    System.out.println("生产:" + msg);
                    //Thread.sleep(500); // 模拟生产速度
                }
                // 发送结束信号
                queue.put("END");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();

        // 消费者线程
        new Thread(() -> {
            try {
                while (true) {
                    String msg = queue.take(); // 自动阻塞(如果队列空)
                    System.out.println("消费:" + msg);
                    Thread.sleep(10000); // 模拟生产速度

                    if ("END".equals(msg)) break; // 收到结束信号退出
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();

    }
}

2、单例模式 双重检测锁

java 复制代码
public class Singleton {
    // 🔴 关键1:用volatile修饰实例变量(禁止指令重排序)
    private static volatile Singleton instance;
    // 私有化构造方法,禁止外部new
    private Singleton() {}
    // 双重检查锁获取实例
    public static Singleton getInstance() {
        // 第一次检查:无锁,快速判断(大部分场景直接返回,提升性能)
        if (instance == null) {
            // 同步锁:保证只有一个线程进入初始化逻辑
            synchronized (Singleton.class) {
                // 第二次检查:防止多个线程等待锁后重复初始化
                if (instance == null) {
                    // 初始化实例(volatile避免指令重排序导致的半初始化问题)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

3、令牌桶

java 复制代码
class TokenBucket {
    private long capacity;    // 桶容量
    private long tokens;      // 当前令牌数
    private long lastTime;    // 上次更新时间
    private long rate;        // 填充速率(ms/个)
    
    public TokenBucket(long capacity, long ratePerSecond) {
        this.capacity = capacity;
        this.tokens = capacity;
        this.lastTime = System.currentTimeMillis();
        this.rate = 1000 / ratePerSecond; // 每个令牌需要的毫秒数
    }
    
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        // 补充令牌
        tokens += (now - lastTime) / rate;
        if (tokens > capacity) tokens = capacity;
        lastTime = now;
        
        // 检查令牌
        if (tokens >= 1) {
            tokens--;
            return true;
        }
        return false;
    }
}
相关推荐
醇氧6 小时前
springAI学习 一
学习·spring·ai·ai编程
菜鸟‍6 小时前
【论文学习】Co-Seg:互提示引导的组织与细胞核分割协同学习
人工智能·学习·算法
YJlio6 小时前
Active Directory 工具学习笔记(10.14):第十章·实战脚本包——AdExplorer/AdInsight/AdRestore 一键化落地
服务器·笔记·学习
东华万里6 小时前
第十四篇 操作符详讲
c语言·学习·大学生专区
nwsuaf_huasir6 小时前
深度学习2-pyTorch学习-张量基本操作
pytorch·深度学习·学习
d111111111d6 小时前
江协科技-PID基本原理-(学习笔记)-主页有所有STM32外设的笔记基本都是万字起步。
笔记·科技·stm32·单片机·嵌入式硬件·学习
啦哈拉哈6 小时前
【Python】知识点零碎学习3
开发语言·python·学习
LO嘉嘉VE6 小时前
学习笔记二十九:贝叶斯决策论
人工智能·笔记·学习
2401_834517077 小时前
AD学习笔记-33 丝印位号的调整
笔记·学习