可以通过分析PriorityBlockingQueue来了解JUC中的线程安全的队列实现的一些套路,这些套路会在JUC中其他数据结构实现上反复出现,从而可以更合理的了解那些实现机制背后通用的部分。
A Queue that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.
阻塞队列,这个接口就非常重要,它是定义了阻塞队列需要实现的接口能力,它的子类有ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue等。
作为队列的扩展,扩展的核心能力是阻塞能力,这个阻塞能力表示:当队列空了的时候,获取元素的操作需要阻塞,等待队列有存储新的元素进入。这里容易产生一个误解:认为BlockingQueue也包含了当队列满的时候,放入操作阻塞的能力,这一点并不是BlockingQueue的能力要求,这和子类实现的是有界或无界队列有关。
PriorityBlockingQueue的数据结构的实现是和PriorityQueue是一致的,完全可以参考前一篇的文章,这里重点是了解清楚通过锁来保证线程安全的队列的实现方式,知道了这些知识点,再要理解JUC中其他的队列的实现简直轻而易举。
实现线程安全的关键两个属性:
/**
* Lock used for all public operations
*/
private final ReentrantLock lock;
/**
* Condition for blocking when empty
*/
private final Condition notEmpty;
各个操作的时候都执行lock.lock();锁住,操作结束执行lock.unlock();解锁。
比如简单的查看元素数量的方法:
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return size;
} finally {
lock.unlock();
}
}
有了锁,实现线程安全的操作变得简单而不易出错。下面我们看一些关键的操作方法的实现。
offer方法将一个元素放入队列,对于一个无界限队列来说,需要处理扩容情况。而作为阻塞队列,当放入元素意味着此时队列不是空队列,那么就需要通知那些来获取队列元素因为空队列而阻塞的线程,继续执行获取元素的操作。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
// 扩容逻辑
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
// 唤醒线程
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
对于一个元素的位置摆放和优先级队列是一致的,完全可以参考前面一篇PriorityQueue。
take方法尝试获取队列头节点的元素,如果为空,就阻塞线程等待,直到队列有元素再唤醒继续执行获取动作。这个Condition notEmpty内部机制可以参考前面的文章
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null)
// 等待唤醒
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
通过notEmpty,实现了阻塞队列的核心能力,那就是当获取元素的时候队列空了阻塞线程和当队列有元素的时候进行唤醒动作。这一点在其他阻塞队列中也这样实现,另外,因为这是个无界队列,并不会发生队列满的情况,所以就没有在放入元素的时候处理阻塞的逻辑,而那些有界队列就需要处理这种情况,当然,处理起来也非常简单,再来一个标记队列满了的Condition就可以了。
这种队列需要扩容,经过前面的一篇,我们已经不再陌生,而对于一个线程安全的队列来说,正在扩容的时候只要确保持有锁,挡住外界的读写操作,就不会有问题。如果你也是这么想的,那么从下面的实现代码中就可以学习到一些优化细节和思路。
private void tryGrow(Object[] array, int oldCap) {
// 解锁操作,这里需要清楚调用这个方法默认是需要保证获得主锁的
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
// 对allocationSpinLock进行cas更新,调用的地方是用while包住的,所以没有进入这个if的话还会自旋
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
// 扩大的容量计算
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
// oldCap最大就能到MAX_ARRAY_SIZE,如果计算出来的newCap大于MAX_ARRAY_SIZE,那么就判断一下oldCap+1是不是已经超过了MAX_ARRAY_SIZE,如果超过就抛出OutOfMemoryError异常,也就是说最后一次扩容最大只能到MAX_ARRAY_SIZE的容量,下一次就不行了
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
int minCap = oldCap + 1;
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
newCap = MAX_ARRAY_SIZE;
}
if (newCap > oldCap && queue == array)
// 新数组
newArray = new Object[newCap];
} finally {
// 设置allocationSpinLock为0 放开扩容操作限制
allocationSpinLock = 0;
}
}
// 这个条件成立意味着cas失败或者allocationSpinLock状态为1,表示有线程正在扩容
if (newArray == null) // back off if another thread is allocating
// 让出CPU
Thread.yield();
lock.lock();
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
扩容操作并没有像其他方法一样上来就抢锁,而是进行了lock.unlock()操作,再看下去发现它是使用原子cas更新allocationSpinLock来保证没有其他线程可以并发执行扩容的逻辑代码。这样就优化了在扩容时对其他操作的性能影响。
在清楚了PriorityQueue数据结构后对于理解PriorityBlockingQueue的实现机制就很简单了,主要需要理解到ReentrantLock和Condition的作用,因为前面已经详细进入过AQS系列的世界,现在看来从基础的开始看起,一些花里胡哨的东西融会贯通也是容易的。
后面和PriorityBlockingQueue有点关联的是DelayedWorkQueue和ScheduledThreadPoolExecutor。路线图大概是这样的:
UNSAFE->AQS->ReentrantLock+PriorityQueue->PriorityBlockingQueue->DelayedWorkQueue->ScheduledThreadPoolExecutor
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于
我使用irb。下面是我写的代码。“斧头”..“bc”我期待"ax""ay""az""ba"bb""bc"但结果只是“斧头”..“bc”我该如何纠正?谢谢。 最佳答案 >puts("ax".."bc").to_aaxayazbabbbc 关于ruby-从结束值创建一系列字符串,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7617092/
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>