JUC并发编程面试题总结

文章目录

  • 1、创建线程的三种方式
  • 2、线程的状态
  • 3、线程的上下文切换
  • 4、run和start的区别
  • 5、sleep和wait区别
  • 6、虚假唤醒,精确唤醒
  • 7、两阶段终止模式
  • 8、多线程下的线程安全问题
  • 9、如何解决线程安全问题
  • 10、synchornized的原理
  • 11、锁升级的机制
  • 12、锁消除
  • 13、批量重偏向
  • 14、park和unpark
  • 15、死锁的条件
  • 16、锁饥饿的问题
  • 17、ReentrantLock
  • 18、java内存模型的组成?三大特性?
  • 19、volatile关键字的作用
  • 20、什么是读写屏障?
  • 21、synchronized能否保证JMM的三大特性
  • 22、手写单例模式,懒汉式,饿汉式,双检锁模式【放在最后,一定要自己手写一遍】
  • 23、什么是CAS,乐观锁机制?
  • 24、CAS的ABA问题
  • 25、原子累加器的原理?
  • 26、什么是缓存行的伪共享问题?
  • 27、什么是不可变对象?如何设计一个不可变类
  • 28、什么是享元模式?在哪些地方有应用?
  • 29、线程池的七大参数和工作原理?
  • 30、拒绝策略?
  • 31、阻塞队列?
  • 32、常见的线程池有哪些?
  • 33、线程数量设置多少比较合适?
  • 34、线程中一些api的区别:
  • 35、如何处理线程池中的异常:
  • 36、手写线程池,了解AQS源码

根据笔记总结JUC并发编程面试题,只讲重点

1、创建线程的三种方式

关键点在于三者间的区别,runnable和callable都是经过thread包装的,不同的是runnable把创建线程和执行任务进行了分离。callable通过futureTask获取任务的结果。

2、线程的状态

在操作系统层面是5种,java的api层面是6种。
创建状态(new),运行状态(run或start)【引出4、run和start的区别】、阻塞状态(synchronized没有竞争到锁)、等待状态(例如wait方法)、有时限的等待状态(例如wait方法带有时间参数)、结束状态。
在这里插入图片描述

3、线程的上下文切换

涉及到cpu任务调度和时间片,程序计数器的概念。在多线程的环境下,某个线程运行时cpu的时间片用尽,就会发生线程上下文切换问题,程序计数器会将当前线程的操作步骤记录,等到再次轮到该线程执行时,就从记录的位置继续执行【引出8、多线程下的线程安全问题】,上下文切换不一定只发生在时间片结束,主动调用yield也可以,但是调度器可能忽略该提示。

4、run和start的区别

run是在主线程中执行,start是另启动一个线程。当调用start()方法时,Java虚拟机会在一个新的线程中调用该线程对象的run()方法。

5、sleep和wait区别

sleep不会释放锁,是thread层面的(当其他线程通过interrupt方法打断正在sleep的线程,sleep方法会抛出interruptedException【引出7、两阶段终止模式】,wait会释放锁,是对象层面的,wait通常和notify和notifyAll一起运用达到线程间的通信【引出6、虚假唤醒,精确唤醒的问题

6、虚假唤醒,精确唤醒

如果有两个线程同时使用wait方法,主线程使用notify只会随机唤醒其中的一个,可能会导致不是自己想要唤醒的线程被唤醒。推荐使用notifyAll唤醒所有,或者使用reentranlock的newCondition.await()配合signal()方法精确唤醒。

7、两阶段终止模式

核心思想在于Sleep+设置打断标记。
isInterrupted():用于检查调用该方法的线程的中断状态。
interrupted():查询当前线程的中断状态,并清除中断状态标志。

8、多线程下的线程安全问题

关键点在于,很多操作在字节码的层面不是原子性的,比如i++,在底层是分为了四步,在执行这四步的过程中,线程发生上下文的切换。读取操作不会造成线程安全问题,多线程对方法中的局部变量的写也不会造成线程安全问题,因为栈和栈之间是独立的。
在这里插入图片描述

9、如何解决线程安全问题

加锁(乐观锁和悲观锁)…【从这一条可以引出下面十条面试题

10、synchornized的原理

首先,使用synchornized需要指定一个对象,如果对象不一样,那么锁是不会生效的。其次,synchornized还可以解决可见性和有序性问题,前提是代码块必须完全被synchornized控制。
表层原理:被synchornized保护的代码块,即使时间片结束,其他线程发现有锁,也无法对资源进行操作,会陷入阻塞(任务调度器不会把时间片分配给阻塞的线程)注意:synchornized不是把并行变成串行
在这里插入图片描述
底层原理:Monitor(监视器)机制。Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针。(使用synchronized的动作让对象和Monitor关联。)

	 Dog dog = new Dog();synchronized (dog){}

在这里插入图片描述

11、锁升级的机制

无锁->偏向锁->轻量级锁->重量级锁
无锁状态不用解释,没加锁就是无锁。
当有A、B两个线程要访问同一个代码块时,B线程不知道什么原因一直阻塞,A线程就可以反复进入代码块,相当于还是单线程的情况,
,JVM 会在对象头里记录下A线程的 ID,之后如果该线程再次请求相同的锁,不需要再进行加锁和解锁操作,直接进入同步块。
在这里插入图片描述
就在这个时候B线程解除了阻塞,JVM 会撤销偏向锁,并将锁状态升级为轻量级锁,尝试通过CAS(Compare And Swap)操作将锁对象头的内容(原偏向锁或无锁状态)更新为当前线程的锁记录(Lock Record)。
在这里插入图片描述
如果争抢锁失败,或者此时又有更多的线程来竞争锁,锁会升级为重量级锁(走synchronized的流程)。
在这里插入图片描述

12、锁消除

如果 JVM 能够确定某个对象不会在多个线程之间共享(也就是不会逃逸到当前线程之外),那么该对象上的锁就没有必要存在,JVM 会在编译时直接将该锁操作去除,这就是锁消除。【相关面试题:jvm:逃逸分析

13、批量重偏向

当 JVM 检测到某个锁对象频繁被不同线程使用时,而这些线程都是相对固定的一组线程,那么 JVM 会进行一次批量重偏向操作,将这些对象的偏向锁重定向到新的线程组,而不是逐个撤销偏向锁。
批量重偏向的触发条件是 JVM 检测到同一批对象频繁进行偏向锁撤销,但是不是属于恶性竞争的情况(有一种情况,虽然是多线程环境,A和B线程要访问某个代码块,但是实际上是A先访问,然后B再访问,这时偏向了线程A的对象仍有机会重新偏向B)如果转换成强竞争,就会执行锁的批量撤销,并将锁升级为轻量级锁或重量级锁。

14、park和unpark

park:挂起线程 unpark:唤醒线程 和wait notify的区别,park和unpark是Thread层面的,不需要配合对象锁使用,并且可以精确唤醒。还可以先unpark再park(这种情况本次park不会生效)。

@Slf4j(topic = "c.Demo1")
public class Demo1 {public static void main(String[] args) throws InterruptedException {//park案例//wait,notify和notifyAll必须配合Object Monitor一起使用,park unpark不用//park unpark可以精准到线程单位进行唤醒//park unpark可以先unparkThread t1 = new Thread(() -> {log.info("start");try {
//                Thread.sleep(1000);//可以先unpark再parkThread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info("park");LockSupport.park();log.info("resume");}, "t1");t1.start();Thread.sleep(1000);log.debug("unpark");LockSupport.unpark(t1);}
}

和sleep的区别是等待时会释放锁。

15、死锁的条件

同一个资源只能被A或者B同一时间持有,并且A在持有资源时等待B释放另一个资源,B同理,以及A和B不能强制占有对方的资源。如何定位死锁?使用jconsole控制台连接当前进程,在线程选项卡检测死锁。【引出17、ReentrantLock
在这里插入图片描述

16、锁饥饿的问题

是非公平锁模式下的问题,典型的非公平锁(synchronized),即一个线程可能会多次竞争到锁,而有的线程则一直获取不到锁。【引出17、ReentrantLock

17、ReentrantLock

ReentrantLock是一种可重入锁,同一个线程可以多次获取同一个对象而不会被阻塞。并且支持公平锁和非公平锁两种模式。并且需要手动地加锁和解锁,还提供了条件变量(condition),可以在某个线程await后使用singal方法精确唤醒。
ReentrantLock如何解决死锁问题?利用tryLock方法,tryLock是尝试获取锁,如果获取不到就直接返回false,不会一直死等,陷入阻塞。还可以设置超时时间。

18、java内存模型的组成?三大特性?

JMM的三大特性 原子性,可见性,有序性
内存模型分为主内存和工作内存。主内存线程共享,每个线程都有自己的工作内存。存储主内存的副本。可能会导致什么问题?数据不一致的问题。
如果某个线程需要频繁从主内存中读取相同的值,JIT编译器就会将值缓存到该线程自己的工作内存中,主内存中的值一旦发生改变,在没有干预的情况下该线程读取到的还是自己工作内存中的值【引出19、volatile关键字的作用
在这里插入图片描述

19、volatile关键字的作用

被volatile修饰的变量,某个线程必须每次从主存中读取该变量的值,所以保证了可见性。同时volatile关键字也可以保证有序性【引出20、读写屏障】。不能保证原子性【引出21、synchronized能否保证JMM的三大特性?

20、什么是读写屏障?

是volatile引出的概念。被volatile关键字修饰的变量,写指令后会加入写屏障,读指令前会加入读屏障。保证在写屏障之前对共享变量的改动都同步到主存,在读屏障之后的读取都是读主存中最新的数据。
在这里插入图片描述在这里插入图片描述

21、synchronized能否保证JMM的三大特性

synchronized可以保证JMM的三大特性,在某个线程获取到锁时,会清空自己工作内存的值,并重新从主存中同步一份,释放锁时,会将自身的改动同步到主存中,从而保证了可见性和有序性。而synchronized本身就可以保证原子性。以上的前提是代码块要完全被synchronized关键字控制。

22、手写单例模式,懒汉式,饿汉式,双检锁模式【放在最后,一定要自己手写一遍】

23、什么是CAS,乐观锁机制?

CAS的意思是比较并交换,首先会从共享变量中获取旧值,在修改值之前,还会从主存中获取最新的值,并且判断旧值和现在主存中最新的值是否一致,如果一致则代表修改期间,没有别的线程对共享变量进行修改,则可以将共享变量的值设置为修改后的值。前提是共享变量需要被volatile修饰。CAS就是乐观锁的一种体现。(在读取数据,操作数据时不会加锁,而是在更新数据时才会检查)【引出24、CAS的ABA问题

24、CAS的ABA问题

即:CAS操作无法感知其他线程是否将共享变量修改,又改回去的情况。解决方式:AtomicStampedReference定义版本号。在进行CAS时不仅会检查旧值和主存中最新的值,还会检查版本号是否相同。

	static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<String>("A",0);//....//获取值String reference = atomicStampedReference.getReference();//获取版本号 0int stamp = atomicStampedReference.getStamp();// 在后续的操作中,除了需要获取值,还需要获取版本号。atomicStampedReference.compareAndSet(reference,"C",stamp,stamp+1)

如果不需要关心版本号,可以使用AtomicMarkableReference标记是否进行过修改。

AtomicMarkableReference<Bag> reference = new AtomicMarkableReference<>(bag,true);

25、原子累加器的原理?

底层使用的是分段锁,使用了一个或多个累加单元(称为Cell),每个单元存储部分的累加值。当多个线程同时更新时,它们会分散到不同的单元上进行操作,减少对单个累加单元的争夺。最终累加结果:当需要获取最终的累加值时,LongAdder.sum() 会将所有单元中的部分值加总,返回累加的最终结果。【有可能会接着问你CurrentHashMap分段锁和AQS的原理,还会引出26、什么是缓存行的伪共享问题

26、什么是缓存行的伪共享问题?

首先要知道缓存行是什么:现代CPU为了提高性能,引入了多级缓存(如L1、L2、L3)。每个缓存以缓存行为单位存储数据,通常一个缓存行的大小是64字节。当一个线程读取内存中的数据时,CPU会将包含该数据的整个缓存行加载到缓存中,这样可以减少频繁访问内存的开销。
重点:当某个线程修改了共享缓存行中的数据,其他线程对应缓存中的数据将标记为无效,触发缓存行的同步或重新加载。
在这里插入图片描述
从而引出了伪共享问题:
多个线程操作不同的变量,这些变量存储在不同的内存位置上。但是这些变量共享同一个缓存行(存在于多线程的环境下)
尽管每个线程访问的变量本身是独立的,但由于它们位于同一个缓存行中,缓存一致性协议导致每次修改都会影响其他线程,形成不必要的缓存行同步,导致性能下降。这种现象称为伪共享,因为实际上这些线程并没有真正共享同一个变量。
解决方式:通过引入额外的填充字段,使得每个变量占用独立的缓存行。
在这里插入图片描述

27、什么是不可变对象?如何设计一个不可变类

不可变类的典型是:String。类和方法都被final修饰,不能让子类继承,重写。并且进行防御性拷贝,每次都返回一个新的字符串对象,保证原有的对象不被修改。防御性拷贝属于深度拷贝

28、什么是享元模式?在哪些地方有应用?

本质:对于一定范围内相同对象的重复使用。例如Long的valueOf方法。(-128~127)
在这里插入图片描述

29、线程池的七大参数和工作原理?

核心线程数,最大线程数,应急线程存活时间,时间单位,拒绝策略,线程工厂,阻塞队列。【从此引出下面n条面试题
工作原理:首先创建线程到核心线程数量,核心线程数量满了,其他的任务放入阻塞队列,阻塞队列也满了,创建剩下任务数量的应急线程直到核心线程数+应急线程数 = 最大线程数,超过这个线程数量的任务就执行丢弃策略。【引出30、拒绝策略

30、拒绝策略?

丢弃等待时间最久的,由发起任务的线程执行,丢弃任务并抛出异常,直接丢弃不作处理,还可以自定义拒绝策略。

31、阻塞队列?

首先要明确什么是阻塞队列?是一种多线程模式下的生产者-消费者模型,生产者将消息放入阻塞队列,消费者从阻塞队列获取消息,当阻塞队列满时,生产者会阻塞,并唤醒消费者,当阻塞队列为空时,消费者会阻塞并唤醒生产者。
常见的阻塞队列有:ArrayBlockingQueue:基于数组的有界阻塞队列,队列的容量在创建时固定,不能动态扩展。它支持公平锁机制,即可以选择按插入顺序公平地处理线程。
LinkedBlockingQueue:可以指定容量也可以不指定。(最大容量受到内存限制)
PriorityBlockingQueue:没有指定容量,支持优先级排序的无界阻塞队列,队列中的元素会根据自然顺序或者自定义的比较器进行排序。

32、常见的线程池有哪些?

重点:【这一条需要结合真实项目说明,不能死背面试题。如何运用线程池,设置参数,并且可以配合CompletableFuture使用】
单线程的线程池,固定大小线程池…
什么场景用到的单线程线程池?比如需要手动执行定时任务,某个定时任务执行的时间要10分钟,但是不能页面上没反应,应该点了执行就直接返回,就可以另外开一个单线程的线程池,提交任务,先返回结果,然后让后台的另一个线程继续执行任务。
什么场景用到的固定大小的线程池?
从第三方系统拉取数据,避免系统资源耗尽,设置5个线程【引出问题33、线程数量设置多少比较合适?】。

33、线程数量设置多少比较合适?

CPU密集型:复杂的数学运算、图像处理等,线程主要在使用 CPU 进行计算,并非处理外部资源 CPU 核心数 + 1
I/O 密集型:主要是 I/O 操作,如文件读写、数据库查询、网络请求等,线程通常会在等待外部资源(磁盘、网络)时处于空闲状态。
线程数 ≈ CPU 核心数 * (1 + 线程等待时间 / 线程工作时间)

34、线程中一些api的区别:

submit和execute:submit可以得到返回的结果,而execute不关心结果。submit底层还是依靠execute工作。
invokeAll和invokeAny: invokeAll会阻塞当前线程,等待所有任务执行完成后返回结果。如果有某个任务发生异常,不会返回后面任务的结果。
invokeAny:会阻塞当前线程,但是会返回最先完成任务的结果,如果最先完成任务抛出异常,则会返回第二个完成任务的结果。
shutDown和shutDownNow:shutDown:主线程不会阻塞,已经提交任务的线程会执行完。
shutDown:不会接受新的任务,同时会将队列中的任务返回。

35、如何处理线程池中的异常:

使用try-catch处理,或利用future的get()方法。

36、手写线程池,了解AQS源码

有条件可以手写实现一个简单的线程池,以及阅读AQS非公平锁模式的源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/58164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity编辑器制作多级下拉菜单

Unity编辑器下拉菜单 大家好&#xff0c;我是阿赵。   在Unity引擎里面编写工具插件&#xff0c;有时候会用到一些特殊的菜单形式&#xff0c;比如下拉选项。 通过下拉菜单&#xff0c;给用户选择不同的选项。   如果只是一层的下拉列表&#xff0c;可以用EditorGUILayout.…

Nginx upstream

什么是Nginx upstream&#xff1f; Nginx 模块一般分为三大类&#xff1a;handler、filter和upstream。 利用 handler、filter 这两个模块&#xff0c;可以使 Nginx 轻松完成任何单机工作。 upstream 模块将使 Nginx 跨越单机的限制&#xff0c;完成网络数据的接收、处理和转…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-23目录1. Advancements in Visual Language Models for Remote Sensing: Datasets, Capabilities, and Enhancement Techniques摘…

Zig 语言通用代码生成器:逻辑,冒烟测试版发布二

Zig 语言通用代码生成器&#xff1a;逻辑&#xff0c;冒烟测试版发布二 Zig 语言是一种新的系统编程语言&#xff0c;其生态位类同与 C&#xff0c;是前一段时间大热的 rust 语言的竞品。它某种意义上的确非常像 rust&#xff0c;尤其是在开发过程中无穷无尽抛错的过程&#x…

高等数学-宋浩版2.0-映射

映射&#xff1a;X,Y为非空集合&#xff0c;存在法则F,对X(原像)中每个元素X&#xff0c;按法则F&#xff0c;在Y中有唯一元素与之对应&#xff0c;F为x到Y&#xff08;镜像&#xff09;的映射。f:X->Y X原像&#xff0c;Y像&#xff0c;x定义域&#xff0c;Df,Rf &#x…

python之多任务爬虫——线程、进程、协程的介绍与使用(16)

文章目录 1、什么是多任务?1.1 进程和线程的概念1.2 多线程与多进程的区别1.3 并发和并行2、python中的全局解释器锁3、多线程执行机制4、python中实现多线程(threading模块)4.1 模块介绍4.2 模块的使用5、python实现多进行程(Multiprocessing模块)5.1 导入模块5.2 模块的…

Caffeine本地缓存框架

Caffeine本地缓存框架 hi&#xff0c;我是阿昌&#xff0c;今天记录一下Java最强本地缓存Caffeine 1、缓存介绍 缓存(Cache)&#xff0c;在软件无处不在。从底层CPU多级缓存&#xff0c;再到客户页面缓存&#xff0c;和服务器数据缓存&#xff0c;导出都存在着缓存的身影&am…

HBuilder X 中Vue.js基础使用2(三)

一、条件渲染 1、条件判断 v-if &#xff1a; 表达式返回真值时才被渲染 v-else &#xff1a;表达式返回为假时不被渲染 2、 分支条件判断 v-else-if &#xff1a;使用v-if , v-else-if 和 v-else 来表示其他的条件分支 3、显示隐藏 v-show v-show true 把节点显示 …

PortQry下载安装使用教程(超详细),Windows测试UDP端口

《网络安全自学教程》 PortQry是微软官方提供的一款TCP/IP连接「排障工具」&#xff0c;用来「检查」TCP/UDP「端口状态」。 平时检查端口状态&#xff0c;最常用的是telnet&#xff0c;但它是基于TCP协议的&#xff0c;无法检测「UDP端口」&#xff0c;这篇文章教大家如何在W…

Axure随机验证码高级交互

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;字母数字随机验证码高级交互 主要内容&#xff1a;4位字母数字随机验证码生成、错误提示与State状态同步 应用场景&#xff1a;登录验证码、其他类…

面试宝典(五):用三个线程按顺序循环打印123三个数字,比如123123123

要使用三个线程按顺序循环打印123三个数字&#xff0c;势必要控制线程的执行顺序&#xff0c;可以使用java.util.concurrent包中的Semaphore类来控制线程的执行顺序。 代码示例 import java.util.concurrent.Semaphore;public class SequentialPrinting123 {private static Se…

leetcode:34. 在排序数组中查找元素的第一个和最后一个位置(python3解法)

#1024程序员节 | 征文# 难度&#xff1a;中等 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(lo…

初识算法 · 前缀和(1)

目录 前言&#xff1a; 一维数组的前缀和 题目解析 算法原理 算法编写 二维数组的前缀和 题目解析 算法原理 算法编写 前言&#xff1a; ​本文的主题是前缀和&#xff0c;通过两道题目讲解&#xff0c;一道是一维数组的模板&#xff0c;一道是二维数组的模板。 链接…

【WebGIS实例】(18)MapboxGL 绘制矢量——线、面

前言 Mapbox GL JS 版本&#xff1a;3.6.0 该博客仅供学习参考&#xff0c;如果您是计划在实际项目中实现该功能&#xff0c;也推荐您直接使用已有的功能库&#xff1a; 官方案例&#xff1a;Draw a polygon and calculate its areamapbox-gl-draw&#xff1a;mapbox/mapbox-g…

基于Django+python的酒店客房入侵检测系统设计与实现

项目运行 需要先安装Python的相关依赖&#xff1a;pymysql&#xff0c;Django3.2.8&#xff0c;pillow 使用pip install 安装 第一步&#xff1a;创建数据库 第二步&#xff1a;执行SQL语句&#xff0c;.sql文件&#xff0c;运行该文件中的SQL语句 第三步&#xff1a;修改源…

HTTPS讲解

前瞻 HTTP与HTTPS的关系 HTTPS也是一个在应用层的协议&#xff0c;是在HTTP协议基础上的一个加密解密层 明文 密文 秘钥 明文->秘钥 加密 秘钥->明文 解密 例如:明文为7 秘钥为2 7^21015&#xff1b; 5就是密文例子: 因为http的内容是明文传输的&#xff0c;明文…

危险物品图像分割系统:一键训练

危险物品图像分割系统源码&#xff06;数据集分享 [yolov8-seg-GFPN&#xff06;yolov8-seg-CSwinTransformer等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global…

LabVIEW共享变量通信故障

问题概述&#xff1a; 在LabVIEW项目中&#xff0c;使用IO服务器创建共享变量&#xff0c;并通过LabVIEW作为从站进行数据通信。通讯在最初运行时正常&#xff0c;但在经过一段时间或几个小时后&#xff0c;VI前面板出现错误输出&#xff0c;导致数据传输失败。虽然“分布式系统…

折扣影票接口对接渠道如何选择?

选择折扣影票接口对接渠道需要综合多方面因素考虑&#xff0c;以下是一些建议&#xff1a; 1.合法性和合规性&#xff1a; 确认供应商资质&#xff1a;优先选择具有相关票务经营资质的渠道。比如一些大型的在线票务平台&#xff0c;它们通常经过官方认证和监管&#xff0c;在…

[JAVAEE] 多线程的案例(二) - 阻塞队列 生产者消费者模型

目录 一. 什么是阻塞队列 二. java中的阻塞队列 三. 生产者消费者模型 3.1 生产者消费者模型与阻塞队列密不可分的关系 3.2 阻塞队列在生产者消费者模型的作用 a. 解耦合 b. 削峰填谷 四. 模拟实现阻塞队列 4.1 实现put方法 4.2 实现take方法 4.3 生产者消费者模型​…