Java并发实用干货

countdownlatch,cyclicbarrier,semaphore的区别,

1和2主要做线程同步的,1主要用于,一个线程等待其他线程执行完成,并且不能重用,2只要用于多个线程相互等待,做完之后,一起乡下,执行,可以重置,3,主要控制资源访问量的,三者,都是基于aqs实现的,要想实现多个线程之间相互同步,可以用reentrlock的多条件等待实现

Semaphore

① 拿许可证

acquire() ------ 阻塞拿,没许可就等着

tryAcquire() ------ 尝试拿,拿不到拉倒

acquireUninterruptibly() ------ 死等拿,天塌了也不停

② 还许可证

release() ------ 还一个,可跨线程还,会累加

③ 查状态

availablePermits() ------ 还剩几个

drainPermits() ------ 全部薅走

getQueueLength() ------ 多少人在等

hasQueuedThreads() ------ 有人在排队吗

java 复制代码
package org.example;

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 使用多个信号量控制多个线程顺序输出 1~100。
 * 信号量数组通过静态代码块初始化,并预先释放第一个信号量。
 */
public class SequentialPrinter {
    // 线程数量(可调整)
    private static final int THREAD_COUNT = 3;
    // 最大输出数字
    private static final int MAX_NUMBER = 100;
    // 每个线程对应的信号量(静态成员)
    private static final Semaphore[] semaphores = new Semaphore[THREAD_COUNT];
    // 当前待打印的数字(原子操作保证线程安全)
    private static final AtomicInteger currentNumber = new AtomicInteger(1);

    // 静态代码块:初始化所有信号量,并让第一个信号量获得一个许可
    static {
        for (int i = 0; i < THREAD_COUNT; i++) {
            semaphores[i] = new Semaphore(0);
        }
        semaphores[0].release();   // 第 0 号线程可以先执行
    }

    /**
     * 打印任务:每个线程持有一个自己的信号量和一个下一个线程的信号量
     */
    static class Printer implements Runnable {
        private final int threadId;
        private final Semaphore mySemaphore;
        private final Semaphore nextSemaphore;

        public Printer(int threadId, Semaphore mySemaphore, Semaphore nextSemaphore) {
            this.threadId = threadId;
            this.mySemaphore = mySemaphore;
            this.nextSemaphore = nextSemaphore;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    // 获取自己的信号量(没有许可则阻塞)
                    mySemaphore.acquire();
                    int num = currentNumber.getAndIncrement();
                    if (num <= MAX_NUMBER) {
                        System.out.println("Thread-" + threadId + " 输出: " + num);
                        // 唤醒下一个线程
                        nextSemaphore.release();
                    } else {
                        // 数字已超限,传递退出信号给下一个线程
                        nextSemaphore.release();
                        break;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 创建并启动线程
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            Semaphore my = semaphores[i];
            Semaphore next = semaphores[(i + 1) % THREAD_COUNT];
            Printer printer = new Printer(i, my, next);
            threads[i] = new Thread(printer, "Printer-" + i);
            threads[i].start();
        }

        // 等待所有线程结束
        for (Thread t : threads) {
            t.join();
        }

        System.out.println("所有线程已退出,1~" + MAX_NUMBER + " 顺序输出完毕。");
    }
}

CountDownLatch 是 Java 并发包(java.util.concurrent)中的一个同步工具类。它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。

可以理解为一个倒计数器:初始计数设置为 N,每调用一次 countDown() 计数减 1,当计数减到 0 时,所有在 await() 上等待的线程被唤醒继续执行。

方法 说明

CountDownLatch(int count) 构造方法,指定初始计数(必须 ≥ 0)。如果 count = 0,则 await() 会立即通过。

void await() 使当前线程等待,直到计数变为 0。如果计数已为 0,则直接返回。可响应中断(抛出 InterruptedException)。

boolean await(long timeout, TimeUnit unit) 带超时的等待。如果超时时间内计数未变为 0,则返回 false;否则返回 true。

void countDown() 将计数减 1。如果减后计数变为 0,则唤醒所有在 await() 上等待的线程。

long getCount() 返回当前计数值(调试或监控用,不是同步安全的通常业务逻辑)。

注意:计数不能重置。当计数变为 0 后,后续 await() 立即通过,countDown() 不再生效。

示例一,主线程等待多个异步任务完成后再处理结果(一等多)

java 复制代码
public class BatchProcess {
    public static void main(String[] args) throws InterruptedException {
        int taskCount = 5;
        CountDownLatch latch = new CountDownLatch(taskCount);
        ExecutorService executor = Executors.newFixedThreadPool(taskCount);

        for (int i = 0; i < taskCount; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    // 模拟异步任务
                    Thread.sleep(1000);
                    System.out.println("任务 " + taskId + " 完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();  // 无论成功/失败,都要减计数
                }
            });
        }

        // 主线程等待所有任务完成
        latch.await();
        System.out.println("所有任务已完成,开始汇总结果...");
        executor.shutdown();
    }
}

示例 2:多个线程同时开始执行(多等一,模拟并发)

java 复制代码
public class ConcurrentStarter {
    public static void main(String[] args) throws InterruptedException {
        int workerCount = 10;
        CountDownLatch startSignal = new CountDownLatch(1);   // 控制起跑
        CountDownLatch doneSignal = new CountDownLatch(workerCount); // 等待完成

        for (int i = 0; i < workerCount; i++) {
            new Thread(() -> {
                try {
                    startSignal.await();   // 所有线程在此等待
                    // 执行并发任务
                    System.out.println(Thread.currentThread().getName() + " 开始工作");
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    doneSignal.countDown();
                }
            }).start();
        }

        System.out.println("准备就绪,3秒后所有线程同时启动...");
        Thread.sleep(3000);
        startSignal.countDown();   // 发令枪响

        doneSignal.await();        // 主线程等待所有工作线程结束
        System.out.println("全部工作完成");
    }
}

示例 3:分布式系统 -- 等待多个服务健康检查完成

java 复制代码
public class ServiceHealthChecker {
    private static final List<String> SERVICES = Arrays.asList("DB", "Redis", "MQ", "ConfigCenter");
    
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(SERVICES.size());
        
        for (String service : SERVICES) {
            new Thread(() -> {
                try {
                    // 模拟检查服务是否可用
                    boolean healthy = checkService(service);
                    if (healthy) {
                        System.out.println(service + " is healthy.");
                    } else {
                        System.err.println(service + " is NOT healthy.");
                    }
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        // 最多等待10秒,所有服务必须就绪
        if (latch.await(10, TimeUnit.SECONDS)) {
            System.out.println("所有服务检查完成,应用启动...");
        } else {
            System.err.println("某些服务未在10秒内响应,启动失败");
            System.exit(1);
        }
    }
    
    private static boolean checkService(String service) throws InterruptedException {
        Thread.sleep(500); // 模拟网络请求
        return true;       // 简化示例
    }
}

CyclicBarrier 是 Java 并发包(java.util.concurrent)中的一个同步辅助类,它允许一组线程互相等待,直到所有线程都到达某个公共屏障点(barrier)后,才能继续执行后续任务。

与 CountDownLatch 不同,CyclicBarrier 的计数器可以循环使用(Cyclic 意为"循环的")。

方法 说明

CyclicBarrier(int parties) 构造方法:设置需要等待的线程数量(parties)。

CyclicBarrier(int parties, Runnable barrierAction) 构造方法:当所有线程都到达屏障时,优先执行 barrierAction(由最后一个到达的线程执行)。

int await() 使当前线程等待,直到所有 parties 都调用了 await()。如果当前线程是最后一个到达的,则会唤醒所有等待线程(并可选执行 barrierAction)。返回当前线程到达的索引(0 表示最后一个)。可响应中断。

int await(long timeout, TimeUnit unit) 带超时的等待,超时后抛出 TimeoutException(其他等待线程会收到 BrokenBarrierException)。

void reset() 将屏障重置到初始状态(如果此时有线程在等待,会抛出 BrokenBarrierException)。

int getNumberWaiting() 返回当前在屏障处等待的线程数量。

boolean isBroken() 判断屏障是否被破坏(例如等待时超时或中断)。

示例 1:基本用法------多线程互相等待,一起继续

java 复制代码
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int threadCount = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            // 所有线程到达屏障后,由最后一个到达的线程执行此任务
            System.out.println("所有线程已到达屏障,继续执行下一阶段...");
        });

        for (int i = 0; i < threadCount; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    System.out.println("线程 " + id + " 开始执行第一阶段任务...");
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println("线程 " + id + " 到达屏障");
                    barrier.await();  // 等待其他线程

                    System.out.println("线程 " + id + " 进入第二阶段");
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println("线程 " + id + " 完成第二阶段");

                    // 屏障可重复使用(如果需要,再调用 barrier.await() 即可)
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出示例(顺序可能不同):

复制代码
线程 0 开始执行第一阶段任务...
线程 1 开始执行第一阶段任务...
线程 2 开始执行第一阶段任务...
线程 1 到达屏障
线程 0 到达屏障
线程 2 到达屏障
所有线程已到达屏障,继续执行下一阶段...
线程 2 进入第二阶段
线程 1 进入第二阶段
线程 0 进入第二阶段
线程 0 完成第二阶段
线程 1 完成第二阶段
线程 2 完成第二阶段

示例 2:可重用的 CyclicBarrier(模拟多轮并发起跑)

java 复制代码
public class ReusableBarrier {
    public static void main(String[] args) {
        int runners = 5;
        CyclicBarrier startLine = new CyclicBarrier(runners, () -> System.out.println("--- 发令枪响,所有运动员起跑 ---"));

        for (int i = 0; i < runners; i++) {
            final int athlete = i;
            new Thread(() -> {
                try {
                    // 第一轮
                    System.out.println("运动员 " + athlete + " 到达起跑线,等待...");
                    startLine.await();
                    System.out.println("运动员 " + athlete + " 开始跑");
                    Thread.sleep(500);
                    System.out.println("运动员 " + athlete + " 第一轮结束");

                    // 第二轮(重用同一个 Barrier)
                    System.out.println("运动员 " + athlete + " 回到起跑线,等待第二轮...");
                    startLine.await();
                    System.out.println("运动员 " + athlete + " 第二轮开始跑");
                    Thread.sleep(500);
                    System.out.println("运动员 " + athlete + " 第二轮结束");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

示例 3:带超时的等待与重置

java 复制代码
CyclicBarrier barrier = new CyclicBarrier(3);
try {
    barrier.await(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    System.out.println("超时,屏障被破坏");
    barrier.reset();  // 重置屏障,其他等待线程会抛出 BrokenBarrierException
}

ReentrantLock 常用方法三段式记忆法

① 拿锁

lock() ------ 阻塞拿,拿不到就等

tryLock() ------ 尝试拿,拿不到拉倒

tryLock(时间) ------ 限时等,超时放弃

lockInterruptibly() ------ 可中断拿,别人 interrupt 我就停

② 还锁

unlock() ------ 必须释放,成对出现,finally 里最稳

③ 高级功能

newCondition() ------ 创建条件变量,精确唤醒condition对象只有唤醒,阻塞两种方法

getHoldCount() ------ 当前线程重入次数

isHeldByCurrentThread() ------ 锁是不是我拿着

getQueueLength() ------ 等锁的有多少人

isFair() ------ 是否公平锁

用法

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;

/**

  • 使用 ReentrantLock + Condition 控制多个线程顺序输出 1~100。

  • 每个线程有一个对应的 Condition,通过锁和条件变量实现精确唤醒。

    */

    public class SequentialPrinterWithLock {

    // 线程数量(可调整)

    private static final int THREAD_COUNT = 3;

    // 最大输出数字

    private static final int MAX_NUMBER = 100;

    // 共享锁和条件数组

    private static final ReentrantLock lock = new ReentrantLock();

    private static final Condition\[\] conditions = new ConditionTHREAD_COUNT;

    // 共享状态

    private static int currentNumber = 1; // 下一个待输出的数字

    private static int currentThreadId = 0; // 当前应该运行的线程编号 (0 ~ THREAD_COUNT-1)

    // 静态代码块初始化所有 Condition

    static {

    for (int i = 0; i < THREAD_COUNT; i++) {

    conditionsi = lock.newCondition();

    }

    }

    /**

    • 每个线程的任务:输出数字并唤醒下一个线程

      */

      static class Printer implements Runnable {

      private final int threadId;

      public Printer(int threadId) {

      this.threadId = threadId;

      }

      @Override

      public void run() {

      while (true) {

      lock.lock();

      try {

      // 如果不是当前线程应该运行的编号,则等待

      while (currentThreadId != threadId) {

      conditionsthreadId.await();

      }

      复制代码
               // 检查是否已经输出完所有数字
               if (currentNumber > MAX_NUMBER) {
                   // 通知下一个线程退出(避免死等)
                   int nextId = (currentThreadId + 1) % THREAD_COUNT;
                   conditions[nextId].signal();
                   break;
               }
      
               // 输出当前数字
               System.out.println("Thread-" + threadId + " 输出: " + currentNumber);
               currentNumber++;
      
               // 切换到下一个线程
               currentThreadId = (currentThreadId + 1) % THREAD_COUNT;
               // 唤醒下一个线程
               conditions[currentThreadId].signal();
      
           } catch (InterruptedException e) {
               Thread.currentThread().interrupt();
               break;
           } finally {
               lock.unlock();
           }
       }

      }

      }

    public static void main(String\[\] args) throws InterruptedException {

    // 创建并启动所有线程

    Thread\[\] threads = new ThreadTHREAD_COUNT;

    for (int i = 0; i < THREAD_COUNT; i++) {

    threadsi = new Thread(new Printer(i), "Printer-" + i);

    threadsi.start();

    }

    复制代码
     // 等待所有线程结束
     for (Thread t : threads) {
         t.join();
     }
    
     System.out.println("所有线程已退出,1~" + MAX_NUMBER + " 顺序输出完毕。");

    }

    }

特性 CyclicBarrier CountDownLatch
可重用性 可重复使用 (调用 reset() 或自动重置) 不可重用,计数器减到 0 后无法恢复
等待线程数 固定数量的线程互相等待(必须相等) 主线程(或任意线程)等待其他 N 个线程完成
构造参数 parties(参与的线程数) count(需要 countDown 的次数)
计数方式 调用 await() 计数减 1(到达屏障) 调用 countDown() 计数减 1
屏障动作 支持 barrierAction(最后一个到达的线程执行)
异常处理 一个线程中断/超时会破坏屏障,其他线程收到 BrokenBarrierException 单个线程中断只影响该线程,不影响其他线程
典型场景 多线程分阶段计算、并行迭代、可重用的并发起跑 主线程等待多个子任务完成、多等一、一等多

reentrantlock和synchronized的区别

synchronized是jvm层面的锁,通过监视器monitor对象实现,在要加锁的字节码前后加上monitorenter,monitorexit字节码指令来实现,自动加锁解锁,默认非公平

ReentrantLock是API级别实现,需要显式lock()和unlock(),但支持公平锁、可中断锁获取、尝试获取锁和多个条件变量。

性能上Java是1.6后两者相当,sync通过锁升级优化了性能

选择时建议:简单场景用synchronized保持代码简洁,

当需要可中断性、超时控制、公平性或多条件等待等高级功能时选用ReentrantLock。使用时必须将unlock()放在finally块中确保锁释放,避免死锁。

在synchronized锁字符串时,

1.字符串可能会被jvm放入常量池,就是内容相同的字符串字面量在内存中只有一个,如果多个锁锁同一对象可能相互干扰,

2.new string("lock")每次产生新的对象,锁的就不是一个对象,无法互斥,

3.锁对象引用被重新赋值后,后续对象使用新锁,锁被破坏,

4.行为不确定性 拼接、intern() 等操作使锁对象的身份难以判断

5.synchronized 的锁对象。最安全、清晰的做法是使用专门的 private final Object 实例作为锁。这样不仅能避免上述陷阱,

相关推荐
Mr.朱鹏1 小时前
基于 postgres_fdw 的跨库查询方案
java·数据库·spring boot·sql·spring·postgresql
1368木林森1 小时前
【Spring源码17·完结篇】SpringBoot核心注解+高频坑点+失效场景万字全集!收官Spring全家桶源码系列
java·spring boot·后端
南山十一少1 小时前
基于 Quartz 组件在 Spring Boot 框架下的周期任务调度实验
java·spring boot·spring
罗超驿1 小时前
14.LeetCode 438 题解:滑动窗口+哈希表找所有字母异位词
java·算法·leetcode
码不停蹄的玄黓1 小时前
Java线程池生命周期
java·开发语言
学习要积极1 小时前
Spring AI Alibaba-ChatClient
java·人工智能·spring
武子康1 小时前
Java-15 深入浅出MyBatis 分页与通用 Mapper 实战:PageHelper + tk.mybatis 从配置到分页查询
java·后端
z落落1 小时前
C# 虚方法(virtual)与抽象方法 +区别+new方法隐藏 & override方法重写
java·开发语言·c#
宋哥转AI1 小时前
Spring AI Graph:从0到Supervisor(二)并行执行+HITL实战
java·agent