java多线程面试大纲
2021-12-29 17:41:12 2 举报
AI智能生成
点我主页,涵盖 Java 后面面试所有知识点,本人持续整理,目前已帮助20+人拿到 大厂Offer
作者其他创作
大纲/内容
Java 中的多线程之锁优化
Synchronized
Synchronized 同步锁实现原理
锁优化
锁升级
动态编译实现锁消除 / 锁粗化
减小锁粒度
Lock同步锁
Lock 锁的实现原理
AQS
ReentrantLock 独占锁
ReentrantReadWriteLock 读写锁
StampedLock - 盖章锁
没有大规模使用:因为不可重入
使用乐观锁优化并行操作
什么是乐观锁?
乐观锁的实现原理
CAS
CPU如何实现原子操作
CPU提供了总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性
CAS 频繁会导致 总线风暴问题
优化 CAS 乐观锁
AtomicInteger AtomicLong 内部使用 Unsafe 的 CAS,不断CAS 会造成 CPU 繁忙
LongAdder:
多线程调优
CPU 上下文切换优化
15 | 多线程调优(上):哪些操作导致了CPU上下文切换?
Linux 使用 vmstat 命令发现 CPU 上下文切换程序
16 | 多线程调优(下):如何优化多线程上下文切换? - CPU 高
上下文切换排查工具:
Linux 下 vmstat 命令
java 中的锁
java中有几种锁?
Synchroized
三种应用方式:
修饰实例方法
当前对象
修饰静态方法
类对象
代码块
给定对象
基于 monitorenter 和 monitorexit 指令实现
简单地说,synchronized修饰,表现为两种锁:
一种是对调用该方法的对象加锁,俗称对象锁或实例锁,
一种是对调用该方法的对象加锁,俗称对象锁或实例锁,
- 另一种是对该类对象加锁,俗称类锁。
对象锁
类锁
sychronized 的实现原理:
synchronized实现原理
synchronized 在 JVM 的实现原理是基于进入和退出管程(Monitor)对象来实现同步。
但 synchronized 关键字实现同步代码块和同步方法的细节不一样,
代码块同步是使用 monitorenter 和 monitorexit 指令实现的,
方法同步通过调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。
synchronized 在 JVM 的实现原理是基于进入和退出管程(Monitor)对象来实现同步。
但 synchronized 关键字实现同步代码块和同步方法的细节不一样,
代码块同步是使用 monitorenter 和 monitorexit 指令实现的,
方法同步通过调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。
内存锁
Java语法规定,任何线程执行同步方法、同步代码块 之前,必须先获取对应的监视器。
JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,
在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,
从而实现JAVA中简单的同步、互斥操作。
JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,
在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,
从而实现JAVA中简单的同步、互斥操作。
锁住代码块时的底层原理?(类锁)
public class SynTest{
public int i;
public void syncTask(){
synchronized (this){
i++;
}
}
}
public int i;
public void syncTask(){
synchronized (this){
i++;
}
}
}
反编译后
锁住方法时的底层原理?(对象锁)
public class SynTest{
public int i;
public synchronized void syncTask(){
i++;
}
}
public int i;
public synchronized void syncTask(){
i++;
}
}
反编译后
jvm 可以从方法常量池中的方法表结构的 ACC_SYNCHRONIZED 的标记区分一个方法是否需要加锁;
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,
如果设置了,执行线程将先持有 monitor, 然后再执行方法,
最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor。
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,
如果设置了,执行线程将先持有 monitor, 然后再执行方法,
最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor。
java 对象头:
Java对象头是synchronized实现的关键,
synchronized用的锁是存在Java对象头里的。
Java对象头是synchronized实现的关键,
synchronized用的锁是存在Java对象头里的。
一个 java 对象包含哪些东西?
在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据 ,对齐填充。
在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据 ,对齐填充。
对象头和 Markword 的关系?
对象头中有一个Mark Word 和 Class Metadata Address 组成;
对象头中有一个Mark Word 和 Class Metadata Address 组成;
Mark Word 和 Sychronized 的关系?
Mark Word 存储了 Sychronized 锁的标注位信息
Mark Word 存储了 Sychronized 锁的标注位信息
Sychronized 锁的四种状态:
无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,
这几种状态会随着竞争情况逐渐升级,但不能降级,目的是为了提高锁和释放锁的效率。
这几种状态会随着竞争情况逐渐升级,但不能降级,目的是为了提高锁和释放锁的效率。
Lock
AQS-- AbstractQueuedSynchronizer
Lock
读写锁 - ReentrantReadWriteLock
可重入锁 - ReetrantLock 实现了Lock接口
lock( )方法
是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。
因此一般来说,使用Lock必须在try{ }catch{ }块中进行,并且将释放锁的操作放在finally块中进行,
以保证锁一定被被释放,防止死锁的发生。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。
因此一般来说,使用Lock必须在try{ }catch{ }块中进行,并且将释放锁的操作放在finally块中进行,
以保证锁一定被被释放,防止死锁的发生。
tryLock( ) 方法
方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,
如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。
在拿不到锁时不会一直在那等待。
如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。
在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法
方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,
在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly( ) 方法
unLock( )方法
与锁相关的几个概念
1.可重入锁
2. 可中断锁
3.公平锁
4.非公平锁
5. 读写锁
AQS图解
AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。
它包含了state变量、加锁线程、等待队列,Condition等并发中的核心组件。
它包含了state变量、加锁线程、等待队列,Condition等并发中的核心组件。
数据结构
state变量
表示加锁的状态。
使用CAS 把 state 由 0 -> 1
加锁线程
表示当前加锁的线程。
Condition
Condition
synchronized与wait()和nitofy()/notifyAll()方法相结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在:
1、一个Lock里面可以创建多个Condition实例,实现多路通知
2、notify()方法进行通知时,被通知的线程是Java虚拟机随机选择的,但是ReentrantLock结合Condition可以实现有选择性地通知,这是非常重要的
synchronized与wait()和nitofy()/notifyAll()方法相结合可以实现等待/通知模型,ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在:
1、一个Lock里面可以创建多个Condition实例,实现多路通知
2、notify()方法进行通知时,被通知的线程是Java虚拟机随机选择的,但是ReentrantLock结合Condition可以实现有选择性地通知,这是非常重要的
wait
阻塞
singnal
唤醒
signalAll
我们要打印1到9这9个数字,由A线程先打印1,2,3,然后由B线程打印4,5,6,然后再由A线程打印7,8,9. 这道题有很多种解法,现在我们使用Condition来做这道题?
//第一个条件当屏幕上输出到3
final Condition reachThreeCondition = lock.newCondition();
//第二个条件当屏幕上输出到6
final Condition reachSixCondition = lock.newCondition();
final Condition reachThreeCondition = lock.newCondition();
//第二个条件当屏幕上输出到6
final Condition reachSixCondition = lock.newCondition();
AQS lock 操作流程图
1,线程A调用lock 方法,先CAS 设置 state 变量
成功:执行逻辑,执行完,state--,唤醒AQS中队列的head 节点的线程(如果有的话)
失败:说明已经有线程获取了锁,判断正在加锁的线程是否是自己
是:state++
否:构建 Node 节点把自己添加到阻塞队列。(使用CAS的方法)
然后判断自己是不是head,是的话就CAS 尝试获取锁,失败的话就阻塞等待唤醒。
AQS 和 ReentrantLock 的关系?
类继承关系
类解释
AQS 提供了基本的加锁释放锁的方法,还有Condition等功能;
ReentrantLock 在内部类中扩展了AQS 的功能,实现了公平锁和非公平锁。
ReentrantLock 在内部类中扩展了AQS 的功能,实现了公平锁和非公平锁。
ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。
这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。
这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。
AQS 使用了模板方法设计模式
使用的时候,需要继承 AbbstractQueuedSychronized 并重写指定的方法;
通常是将子类定义为同步组件的静态内部类。
使用的时候,需要继承 AbbstractQueuedSychronized 并重写指定的方法;
通常是将子类定义为同步组件的静态内部类。
公平锁与非公平锁 (ReentrantLock 构造函数可以选择使用公平锁和非公平锁)
ReentrantLock 内部基于 AQS 对象 实现了公平锁和非公平锁
非公平锁: 不用判断AQS的等待队列中有没有正在等待加锁的线程,直接对state变量进行CAS操作
公平锁:需要先判断AQS的等待队列中有没有等待加锁的线程,有就把自己添加到等待队列中中,没有在进行CAS进行加锁操作
Synchroized 和 Lock 对比
有了Synchroized为什么还要Lock?
Lock提供了比Synchroized更多的功能,其中使用上需要注意:
简述synchronized和java.util.concurrent.locks.Lock的异同 ?
synchroized
(synchroized现在已经得到了很大的优化,不会比lock差太多,锁会自动升级)
(synchroized现在已经得到了很大的优化,不会比lock差太多,锁会自动升级)
作用对象
修饰实例方法
修饰静态方法
修饰代码块
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
volatile
总线风暴问题?
多核处理器中,所有 CPU 共用一条数据总线,CPU 缓存都是靠总线和主内存进行数据交互.
当主内存存在于多个 CPU 缓存中,而其中一个 CPU 更新的缓存,就会触发嗅探机制通过总线通知所有的 CPU,
上面这种方式称为缓存一致性流量
当主内存存在于多个 CPU 缓存中,而其中一个 CPU 更新的缓存,就会触发嗅探机制通过总线通知所有的 CPU,
上面这种方式称为缓存一致性流量
java 中的CAS 底层是使用由 cpp 使用汇编实现, 底层使用 CPU 的 Lock 指令,如果短时间内有大量的 CAS 操作 加上 volatile 的嗅探机制,就会不断地向总线宽带发送消息,导致总线宽带激增,产生总线风暴.
可见性
有序性
有序性
volatile 能使得一个非原子操作变成原子操作吗?
能!
回答这个之前要先说一下 volatile 的作用:内存可见性,禁止指令重排。并没有保证原子性的功能。
但是对 两个 java 的基本数据类型 long 和 double 例外,因为 long 类型的变量读取不是原子的,
先读取前 32 位,再读取后 32 位。但是如果被 volatile 修饰,那就是可以保证原子操作
回答这个之前要先说一下 volatile 的作用:内存可见性,禁止指令重排。并没有保证原子性的功能。
但是对 两个 java 的基本数据类型 long 和 double 例外,因为 long 类型的变量读取不是原子的,
先读取前 32 位,再读取后 32 位。但是如果被 volatile 修饰,那就是可以保证原子操作
volatile 修饰符使用实践?
volatile 怎么实现内存可见性的?-- 内存屏障
volatile 怎么实现内存可见性的?-- 内存屏障
图
死锁,活锁,饥饿 是什么以及它们的区别?
死锁
指两个或两个以上线程在执行中,因争夺资源造成的一种互相等待的现象。
活锁
由于某些条件不满足,导致一直重复尝试,失败,尝试,失败。。。
死锁与活锁的区别?
饥饿
一个或者多个线程由于无法获取所需资源,导致一直无法执行的状态。
导致线程饥饿的原因?
抢占式调度+线程优先级
当一个线程进入一个对象的 sychronized 修饰的实例方法后,
其它线程是否还可以进入其它非 sychronized 修饰的方法?
其它线程是否还可以进入其它非 sychronized 修饰的方法?
可以。如果其他方法没有 synchronized的话,其他线程是可以进入的
所以要开放一个线程安全的对象时,得保证每个方法都是线程安全的
所以要开放一个线程安全的对象时,得保证每个方法都是线程安全的
一个线程在访问一个对象的同步方法时,另一个线程可以同时访问这个对象的非同步方法。
一个线程在访问一个对象的同步方法时,另一个线程不能同时访问这个同步方法。
一个线程在访问一个对象的同步方法时,另一个线程不能同时访问这个对象的另一个同步方法。
多线程
进程与线程的区别?
进程:是操作系统资源分配的最小单元。
线程: 是操作系统资源调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程
线程: 是操作系统资源调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程
进程间的几种通信方式说一下?
线程间的通信方式?
线程间的通信主要用来做线程同步
java 中有几种方法可以实现一个线程?
继承 Thread 类 --> run()
实现 Runnable 接口 ---> run()
实现 Callable 接口 --> call(),推荐,可以在线程结束时在 call 方法中返回值
什么是 Callable 和 Future 和 FutureTask?
Callable 被线程执行后用来产生结果,Future 用来获取结果。
FutureTask
Thread 的生命周期?
java的线程与通用线程生命周期?
JAVA的线程生命周期模型在操作系统的基础上稍作了修改,
一是Runnable表示可运行与运行状态(等待资源依然是Runnable),
二是将休眠状态扩展成了Blocked&Waiting&Time_waiting三种状态。
一是Runnable表示可运行与运行状态(等待资源依然是Runnable),
二是将休眠状态扩展成了Blocked&Waiting&Time_waiting三种状态。
操作系统的五个状态?
初始状态(New)
当是使用 new 创建一个线程后,线程处于新建状态,
此时由JVM为其分配内存,并初始化成员变量的值。
此时由JVM为其分配内存,并初始化成员变量的值。
就绪状态(Runnable)
当线程调用了 start() 方法后,就处于 就处于 Runnable 状态,
JVM会为其创建方法调用栈和程序计数器,等待调度运行。
JVM会为其创建方法调用栈和程序计数器,等待调度运行。
完事具备只欠CPU
运行状态(Running)-java中不存在
处于 Runnable 状态的线程获得CPU,开始执行 run() 方法,该线程处于 Running 状态。
Running状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量比如 sychronized 锁),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到Runnable状态。
Running状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量比如 sychronized 锁),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到Runnable状态。
休眠状态(Blocked,Waiting,TimedWaiting)
当处于 Running 状态的线程失去CPU,变进入 休眠 状态。
终止状态(TERMINATED)
stop(),interrupt() 线程执行完毕,或者异常退出。该线程结束生命周期。
stop() 会强制杀死这个线程,可能导致 Lock 锁不会释放,导致死锁发生,不推荐使用。
如何停止一个正在运行的线程?
一,使用共享变量 boolean 标记
二,使用 Thread.interrupt() 方法,该方法虽然不会中断一个正在运行的线程,
但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态。
但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态。
多线程协调(看链接,讲的非常好)
CountDownLatch(计时器)
运动员起跑程序
构造函数设置计数器起始值,countDown() 计数器减一,当计数器减到0 时,readyLatch.await() 不再阻塞。
CyclicBarrier(循环屏障)
适用于以下场景:
各个线程都必须完成某项任务(写数据)才能继续做后续的任务;
各个线程需要相互等待,不能独善其身。
各个线程需要相互等待,不能独善其身。
运动员准备起跑程序
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getId() + ":我宣布,所有小伙伴写入数据完毕");
}
});
barrier 有5个参与者,只有 5个参与者(线程)可以拿到 barrier 调用其 await() 方法,进行等待,
barrier 被调用了 5次,就会执行后面的 runnabble .
当然也可以给 await() 方法设置超时时间,超时抛出异常。
public void run() {
System.out.println(Thread.currentThread().getId() + ":我宣布,所有小伙伴写入数据完毕");
}
});
barrier 有5个参与者,只有 5个参与者(线程)可以拿到 barrier 调用其 await() 方法,进行等待,
barrier 被调用了 5次,就会执行后面的 runnabble .
当然也可以给 await() 方法设置超时时间,超时抛出异常。
在java 多线程编程中你有那些最佳实践?
a)给线程命名,这样可以帮助调试。
b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步
c)如果可以,更偏向于使用 volatile而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 waito和 notify()来实现线程间
通信,如 Blockingqueue, Countdownlatch及 Semaphore
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
f)将线程和任务分离,使用线程池执行 Runnable 和 Callable
b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步
c)如果可以,更偏向于使用 volatile而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 waito和 notify()来实现线程间
通信,如 Blockingqueue, Countdownlatch及 Semaphore
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
f)将线程和任务分离,使用线程池执行 Runnable 和 Callable
什么是 CopyOnWriteArrayList?可用于什么场景?
免锁容器。好处是当有多个线程同事遍历修改这个 列表时,不会发生
ConcurrentModificationException 异常。
ConcurrentModificationException 异常。
在 CopyOnWriteArrayList 中,写入操作会导致创建整个底层数组的副本,原数组会保留在原地。
是的副本数组在被修改时,读取操作可以安全执行。
是的副本数组在被修改时,读取操作可以安全执行。
缺点:
由于写操作会拷贝数组,消耗内存,如果数组过大,可能会导致频繁 Yong GC 或者 Full GC
不能用于实时读的场景,拷贝数组需要时间,所以调用 set 操作后,读取的值可能还是旧的。
虽然 CopyOnWriteArrayList 可以做到最终一致性,但无法保证实时性。
虽然 CopyOnWriteArrayList 可以做到最终一致性,但无法保证实时性。
设计思想:
读写分离。
使用额外空间的思路,解决并发冲突。
最终一致性。
线程池
为什么使用 Executor 框架?
直接 new Thread 的缺点?
耗费资源
不能控制线程数量
一些特殊功能比如定时执行的线程,不方便
Executor 框架的优点?
复用线程
控制线程数量
使用简单
Executor 和 Executors 的区别?
ForkJoin 框架?
线程池?
任务调度流程图
线程池底层原理?
所有的任务都在workQueue 中, workerQueue 是 BlockingQueue 的实现类实现,Worker 实现了 Runnable 接口,在run方法中维持一个循环判断队列是否有任务需要执行
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
线程池的 submit() 和 execute() 方法有什么区别?
两个方法都可以向线程池中提交任务。
submit() 方法定义在 Executor 接口中,返回类型是 void
execute() 方法定义在 ExecutorService 接口中,返回持有运算结果的 Future 对象。
ThreadPoolExecutor
FixedThreadPool(固定线程数线程池)
CachedThreadPool(可缓存的线程池)
SingleThreadExecutor(单线程池:创建一个核心线程)
ScheduledThreadPool(固定数量,支持定时,和周期的线程)
DelayQueue 实现
实现Runnable接口和Callable接口的区别?
执行execute()方法和submit()方法的区别是什么呢?
Java线程池有哪些参数?阻塞队列有几种?拒绝策略有几种?
线程池的 6 个参数
四种拒绝策略
三种阻塞队列
四种拒绝策略
三种阻塞队列
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列(三种)
handler: 线程池对拒绝任务的处理策略(四种)
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列(三种)
handler: 线程池对拒绝任务的处理策略(四种)
流程
四种拒绝策略
四种拒绝策略:
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
三种阻塞队列
ArrayBlockingQueue
有界
基于数组的先进先出
LinkedBlockQueue
无界
基于链表的先进先出
SynchronizedQueue
无界
无缓冲的等待队列
什么是阻塞队列(BlockingQueue)?
Block ingQueue 是一个支持两个附加操作的队列接口。
* 在队列为空时,获取元素的线程会等待队列有值;
* 在队列已满时,存储元素的线程会等待队列可用。
* 在队列为空时,获取元素的线程会等待队列有值;
* 在队列已满时,存储元素的线程会等待队列可用。
有很多实现类
JDK7提供了7个阻塞队列。分别是
Arrayblockingqueue :一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界阻塞队列
PriorityblockingQueue:一个支持优先级排序的无界阻塞队列
De layQueue:一个使用优先级队列实现的无界阻塞队列
SynchronousQueue:一个不存储元素的阻塞队列。
Linkedtransfer@ueue:一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
Arrayblockingqueue :一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界阻塞队列
PriorityblockingQueue:一个支持优先级排序的无界阻塞队列
De layQueue:一个使用优先级队列实现的无界阻塞队列
SynchronousQueue:一个不存储元素的阻塞队列。
Linkedtransfer@ueue:一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
take() 方法没有值的时候会阻塞,阻塞使用的是 Conidtion 的await()方法实现的。
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
Queue
定义的方法说明
0 条评论
下一页