JUC并发编程之同步器(Semaphore、CountDownLatch、CyclicBarrier、Exchanger、CompletableFuture)附带相关面试题

简介: 1.Semaphore(资源调度) 2.CountDownLatch(子线程优先) 3.CyclicBarrier(栅栏) 4.Exchanger(公共交换区) 5.CompletableFuture(异步编程)

 


1.Semaphore(资源调度)

       由于系统资源并不是无限的,如果多线程的无度索取的话,会对系统造成非常大的负担。在JUC中引入了Semaphore资源调度,通过设置固定的资源量,让线程进行争取。最常见的就是业务办理时候虽然人很多,但是办理窗口是有限的。不是每个人一个窗口。

以下是Semaphore类的常用方法:

方法签名 说明
Semaphore(int permits) 构造一个具有给定许可数的信号量,并设置为非公平模式
Semaphore(int permit, boolean fair) 构造一个具有给定许可数的信号量,并根据公平性参数指定是否在获取信号量时使用先进先出的排序
acquire() 从信号量获取一个许可,如果没有可用的许可,则阻塞直到有可用的许可
availablePermits() 返回当前可用的许可数
release() 释放一个许可,将其返回给信号量

案例代码:证件办理,有10个人需要办理某证件但是只有两个办理业务窗口

package Example2118;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class javaDemo {
    public static void main(String[] args) {
//        只有两个办理证件窗口
        Semaphore semaphore = new Semaphore(2);
//        十个线程客户
        for (int i=0;i<10;i++){
            new Thread(()->{
                try {
//                    排队抢在前面
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到首位");
//                    业务办理与延迟时间
                    System.out.println("开始进行业务办理");
                    TimeUnit.SECONDS.sleep(2);
//                    业务办理结束并释放资源
                    System.out.println("一轮业务办理结束");
                    semaphore.release();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },"客户"+i).start();
        }
    }
}

image.gif

image.gif编辑

面试题:你了解过哪些同步器?请分别介绍下。(1)

(1)Semaphore同步器

特征:

经典的信号量,通过计数器控制对共享资源的访问

Semaphore(int count):创建拥有count个许可证的信号量

acquire()/acquire(int num) : 获取1/num个许可证

release/release(int num) : 释放1/num个许可证


2.CountDownLatch(子线程优先)

       在开发中有时候会遇到一个程序的运行,需要先开启完所有的服务才能运行。意思是必须先让所有子线程运行完主线程才会运行。就像是考试一样,需要先复习完所有的学科才进行考试。所以JUC引入了CountDownLatch

以下是CountDownLatch类的常用方法:

方法签名 说明
CountDownLatch(int count) 构造一个具有给定计数的CountDownLatch对象
await() 导致当前线程等待,直到计数到达零,除非线程被中断
countdown() 递减计数器的计数,如果计数达到零,则释放所有等待的线程
getCount() 返回当前计数

案例代码:

       在校园中举办了校运会,现在有五位选手进行50米的比赛,当所有选手都准备好的时候裁判才会鸣枪指示。其中有位选手在准备前突然口渴,需要喝完水再准备比赛。

package Example2119;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class javaDemo {
    public static void main(String[] args)throws  Exception {
//        五位选手入场
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i=1;i<=5;i++){
            new Thread(()->{
                try {
                    if (Thread.currentThread().getName().equals("选手3")){
                        System.out.println("选手3喝水中");
//                        突然口渴了去喝个水
                        TimeUnit.SECONDS.sleep(3);
                    }
//                    当选手就绪时候等待计数器减少1
                    System.out.println(Thread.currentThread().getName()+"已经就绪");
                    countDownLatch.countDown();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },"选手"+i).start();
        }
//        触发线程等待
        countDownLatch.await();
        System.out.println("所有选手都准备好了,准备开始!");
    }
}

image.gif

image.gif编辑

面试题:你了解过哪些同步器?请分别介绍下。(2)

(2)CountDownLatch同步器

特征:

必须发生指定数量的事件后才可以继续运行(比如赛跑比赛,裁判喊出3,2,1之后大家才同时跑)

CountDownLatch(int count):必须发生count个数量才可以打开锁存器

await:等待锁存器

countDown:触发事件


3.CyclicBarrier(栅栏)

        与CountDownLatch类似,JUC提供了一个CyclicBarrier,其在程序中设置一个公共屏障类把已经到达的线程隔绝在外让其等待,当线程数量足够满足公共屏障的条件时候,则一起运行。注意栅栏并不阻碍主线程,也就意味着主线程和子线程都可以正常运行。

CyclicBarrier类的常用方法:

方法签名 说明
CyclicBarrier(int parties) 创建一个新的 CyclicBarrier,当指定数量的线程(parties)调用 await() 方法时,它将打开屏障
CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier,当指定数量的线程(parties)调用 await() 方法时,它将执行给定的 barrierAction,然后打开屏障
await() 导致当前线程等待,直到所有参与者线程都达到屏障位置
await(timeout) 导致当前线程等待,直到所有参与者线程都达到屏障位置,或者超过指定的超时时间
getNumber() 返回当前在屏障处等待的参与者数目
reset() 将屏障重置为其初始状态
isBroken() 查询当前屏障是否处于破坏状态
getParties() 返回需要参与屏障的参与者数目

案例:

设置多股水流,一旦水流超过界限值,则冲垮大坝。然后过一段时间后重新修复大坝

package Example2120;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class javaDemo {
    public static void main(String[] args)throws  Exception{
//        当出现十股超大型水流汇聚时候大坝就无法承受
        CyclicBarrier barrier = new CyclicBarrier(10,()->{
            System.out.println("大坝:太重啦我承受不了了,裂开!");
        });
        for (int i=1;i<=10;i++){
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"汇聚"+",当前已经汇聚"+",正在等待其他水流汇聚");
//                    等待暴风雨来临
                    TimeUnit.SECONDS.sleep(1);
                    barrier.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },"第"+i+"股水流").start();
        }
        TimeUnit.SECONDS.sleep(3);
//        大坝修复工作
        System.out.println("修复大坝");
        barrier.reset();
        System.out.println("大坝现在的承受量为"+barrier.getParties()+"股水流");
    }
}

image.gif

image.gif编辑

 面试题:你了解过哪些同步器?请分别介绍下。(3)

(3)CyclicBarrier同步器

特征:

适用于只有多个线程都到达预定点时才可以继续执行(比如斗地主,需要等齐三个人才开始)

CyclicBarrier(int num) :等待线程的数量

CyclicBarrier(int num, Runnable action) :等待线程的数量以及所有线程到达后的操作

await() : 到达临界点后暂停线程


4.Exchanger(公共交换区)

       由于消费者-生产者模型中数据交换需要一个独立的空间,所以JUC专门为此设计了一个Exchanger泛型类。并且使用条件一定需要多个线程。

Exchanger<E>类方法:

方法名 方法描述
exchange(V x) 在当前线程中调用此方法并传递数据 x,然后等待另一个线程也调用 exchange() 方法,在交换时将自己的数据与对方交换,并返回对方线程传递过来的数据
exchange(V x, long timeout, TimeUnit unit) 在当前线程中调用此方法并传递数据 x,然后等待另一个线程也调用 exchange() 方法,在交换时将自己的数据与对方交换,并返回对方线程传递过来的数据。如果超过指定的超时时间,将抛出 TimeoutException 异常
exchange(V x, long timeout, TimeUnit unit, boolean timed) 在当前线程中调用此方法并传递数据 x,然后等待另一个线程也调用 exchange() 方法,在交换时将自己的数据与对方交换,并返回对方线程传递过来的数据。如果超过指定的超时时间,将根据 timed 参数决定是否抛出 TimeoutException 异常
toString() 返回此 Exchanger 对象的字符串表示形式

案例:展示通过Exchanger实现生产者-消费者模型(自助餐中厨师和客人)

package Example2121;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
public class javaDemo {
    public static void main(String[] args) throws Exception{
        String dinners[] = new String[]{"番茄炒蛋", "宫保鸡丁", "麻婆豆腐", "红烧肉", "清蒸鲈鱼", "香辣虾", "回锅肉", "鱼香肉丝", "干煸豆角", "糖醋排骨"
        };
//        创建交换空间
        Exchanger<String> dinner = new Exchanger<>();
//        厨师线程
        new Thread(()->{
          try {
              for (int i =1;i<=10;i++){
//                  厨师正在做菜
                  TimeUnit.SECONDS.sleep(2);
//                  做好了饭菜
                  dinner.exchange(dinners[i]);
                  System.out.println("厨师做好了"+dinners[i]+"并端上了桌");
              }
          }catch (Exception e){
              e.printStackTrace();
          }
        },"厨师").start();
//        顾客线程
        new Thread(()->{
            String data = null;
            try {
                while (true){
//                    顾客拿去交换区的食物
                    data = dinner.exchange(null);
//                    顾客享用饭菜并等待下一道菜
                    System.out.println("客人正在享用"+data);
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("享用完毕,正在等待下一道菜");
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        },"客人").start();
    }
}

image.gif

image.gif编辑


5.CompletableFuture(异步编程)

       在JDK1.8以后,JUC提供了Completable简化了异步编程的复杂性.

CompletableFuture常用方法:

方法名 方法描述
T get() 阻塞当前线程,直到结果可用,并返回结果值
void complete(T value) 手动完成计算,并设置计算结果为指定的值

案例:在校运会跑步比赛时候,只有当裁判鸣枪时候才能起步冲刺

package Example2122;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class javaDemo {
    public static void main(String[] args) {
        CompletableFuture<String> future = new CompletableFuture<>();
        Random random = new Random();
        for (int i=1;i<=3;i++){
            new Thread(()->{
                System.out.println("选手准备好了,正在等待枪声");
                try {
//                    开枪啦,跑步
                    System.out.println("选手听到"+future.get());
                    TimeUnit.SECONDS.sleep(random.nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"到达终点");
                }catch (Exception e){
                }
            },"选手-"+i).start();
        }
        new Thread(()->{
            try {
//                倒计时3秒开枪
                TimeUnit.SECONDS.sleep(3);
                future.complete("开枪啦");
            }catch (Exception e){
                e.printStackTrace();
            }
        },"裁判").start();
    }
}

image.gif

image.gif编辑

面试题:AQS 对资源的共享方式?

(1)Exclusive(独占)

只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

   公平锁:按照线程在队列中的排队顺序,先到者先拿到锁

   非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

(2)Share(共享)

多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。


目录
相关文章
|
7月前
|
设计模式 监控 安全
JUC第一讲:Java并发知识体系详解 + 面试题汇总(P6熟练 P7精通)
JUC第一讲:Java并发知识体系详解 + 面试题汇总(P6熟练 P7精通)
537 0
|
8月前
|
安全 算法 Java
去某东面试遇到并发编程问题:如何安全地中断一个正在运行的线程
一个位5年的小伙伴去某东面试被一道并发编程的面试题给Pass了,说”如何中断一个正在运行中的线程?,这个问题很多工作2年的都知道,实在是有些遗憾。 今天,我给大家来分享一下我的回答。
65 0
|
7天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
16 1
|
18天前
|
Java Go 调度
Go语言并发编程原理与实践:面试经验与必备知识点解析
【4月更文挑战第12天】本文分享了Go语言并发编程在面试中的重要性,包括必备知识点和面试经验。核心知识点涵盖Goroutines、Channels、Select、Mutex、Sync包、Context和错误处理。面试策略强调结构化回答、代码示例及实战经历。同时,解析了Goroutine与线程的区别、Channel实现生产者消费者模式、避免死锁的方法以及Context包的作用和应用场景。通过理论与实践的结合,助你成功应对Go并发编程面试。
21 3
|
5月前
|
NoSQL Java 关系型数据库
2024最新500道Java高岗面试题:数据库+微服务 +SSM+并发编程+..
今天分享给大家的都是目前主流企业使用最高频的面试题库,也都是 Java 版本升级之后,重新整理归纳的最新答案,会让面试者少走很多不必要的弯路。同时每个专题都做到了详尽的面试解析文档,以确保每个阶段的读者都能看得懂。
|
7月前
|
安全 算法 Java
JUC第十五讲:JUC集合 - 面试 ConcurrentHashMap 看这篇就够了
JUC第十五讲:JUC集合 - 面试 ConcurrentHashMap 看这篇就够了
|
7月前
全到哭!从面试到架构,阿里大佬用五部分就把高并发编程讲清楚了
不知道大家最近去面试过没有?有去面试过的小伙伴应该会知道现在互联网企业招聘对于“高并发”这块的考察可以说是越来越注重了。基本上你简历上有高并发相关经验,就能成为企业优先考虑的候选人。其原因在于,企业真正需要的是能独立解决问题的人才。每年面试找工作的人很多,技术水平也是高低不一,而并发编程却一直是让大家很头疼的事情,很多人总觉得自己似乎掌握了并发编程的知识,但实际在面试或者工作中,都会被它吊打虐哭。
111 0
|
8月前
|
缓存 安全 Java
Java并发编程必知必会面试连环炮
Java并发编程必知必会面试连环炮
113 0
|
9月前
|
Java
第二季:6CountDownLatch/CyclicBarrier/Semaphore使用过吗?【Java面试题】
第二季:6CountDownLatch/CyclicBarrier/Semaphore使用过吗?【Java面试题】
22 0
|
9月前
|
存储 缓存 安全
多线程与并发编程面试题
多线程与并发编程
40 0
多线程与并发编程面试题