- 浏览: 194546 次
- 性别:
- 来自: 杭州
-
文章分类
最新评论
-
happylouis:
...
我的理解--关于jmx -
whmwg:
...
我的理解--关于jmx -
neusoft_jerry:
哥,你几年后再回来看看这里你翻译过的东西,你自己能看懂吗?
blazeds通道、端点配置 -
zhongliangjun1:
写得非常好,受益良多!
AQS:ReentrantLock源码分析 -
luckywnj:
赞一个,jmx刚开始起步
我的理解--关于jmx
JUC之 AQS 状态依赖的抽象
AQS全称为:AbstractQueuedSynchronizer,它是juc的synchronizer的基础
状态依赖的管理
在JUC中,不管是FutureTask、CountDownLatch、Lock、还是信号量,CyclicBarrier,从某种角度来说,他们都是依赖某种状态,或者说条件,虽然这些条件的值不同:
- 1.FutureTask等待任务结束
- 2.CountDownLatch等待计数器到0
- 3.Lock在等待其他线程释放锁
- 4.信号量在等待获得允许
- 5.CyclicBarrier在等待计数器到0
从某个角度来说,都是在做下面的事情(另一个线程会发送notify消息唤醒等待的线程):
while (!state){ wait(); } change state();
在单线程的程序中,进入等待状态,那么永远也不能被唤醒了,在并发程序中,基于状态的条件是会在其他线程中改变的,因此就是其他线程就需要被阻塞,直到可以继续.
依赖状态在等待的时候可以理解为进入了一个FIFO队列,前面提到的一些类,就是在状态未满足的情况下阻塞,并且加入队列,在合适的时机,唤醒。Java提供了这样的内部队列,但是它是和锁相关的,每个对象都有一把锁,每把锁都有对应的队列,Object中的wait、notify、notifyAll就是操作这个队列的API(参考系列2,话说,话说java这也太不直观了)。从这个角度来看,要操作锁的队列,需要先有锁,因此wait/notify需要在锁中调用,另外,这也确保了,在观察/操作状态的时候,不会有其他线程修改状态!类似这样:
Syn{ while (!state){ wait(); } change state(); }
AQS 状态
前面提到了状态以后,以及wait/notify的队列支持,但是内部锁很不灵活,wait/notify用起来也有很多麻烦的地方,特别是多个线程等待因为不同的原因等待同一个对象上的消息的时候,非常不直观,因此FutureTask/CountDownLatch等是用另一种方式实现的,看ReentrantLock的源码,会发现lock、tryLock等方法都是调用内部类Sync实现,Sync又是继承自AQS,其他类也是这样的,AQS是这些synchronizer类的基础。
状态依赖的抽象
AQS将上面的状态依赖合理抽象,设计理念主要有两部分:
- 1.状态,决定了什么时候需要阻塞,每种synchronizer可能不同,拿每个人自己的业务应用来说,可能是判断XX集合是否为空等等,这些都可以通过实现抽象方法tryAcquire实现,只要返回boolean类型表明状态是否ok就好了,不管你是什么状态。
- 2.等待队列,阻塞的线程去的地方,都是一个队列里的统一操作
状态主要由字段state、getSate、setState、compareAndSetState来表达、操作,状态的原子操作非常重要,可以保证实现synchronizer的语义,否则就会有并发问题;队列中保存的不仅仅是线程信息,它保存的是AQS的一个内部类,Node,它的结构后面介绍
在进行一个因为某些原因可能阻塞的操作时,大致流程是下面这样的:
2和3、5的操作,是需要不同的synchronizer自己实现的:
ReentrantLock判断state是否为0,这时将当前线程设置为拥有者并且修改状态改为1,否则挂起当前线程,加入等待队列
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 独占锁,判断状态是否为0 if (c == 0) { //通过原子操作设置状态 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
CountDownLatch则是判断状态是否减为0,不是的话则挂起线程,放入阻塞队列;
//如果失败,表示状态没有ok,需要放入等待队列,并且通过LockSupport挂起线程 public int tryAcquireShared(int acquires) { return getState() == 0? 1 : -1; }
Semaphore则是判断是否还有可用的许可,也就是判断(available-acquires)是否大于0,如果许可是1的话,其实也就是个独占锁,否则则挂起线程,放入阻塞队列;
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
FutureTask的条件是判断任务是否结束:
protected int tryAcquireShared(int ignore) { //判断状态是被已经ok还是cancel了 return innerIsDone()? 1 : -1; }
在调用Get方法的时候,如果状态没有ok,会被放入队列,阻塞:
V innerGet() throws InterruptedException, ExecutionException {
//here acquireSharedInterruptibly(0); if (getState() == CANCELLED) throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; }
对应步骤5,ReentrantLock调用unLock,会修改state为0,并且设置锁拥有者为null,如果有等待中的线程,唤醒一个:
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
CountDownLatch会调用countDown方法,每次state减1,直到state为0,表示状态ok,唤醒所有其他线程。
public boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; // 注意状态的修改都是原子操作,并且都是for循环里的 if (compareAndSetState(c, nextc)) return nextc == 0; } }Semaphore会调用release方法,每次将许可加1,这样其他线程可以重新获得许可:如果队列里有阻塞的线程的话,唤醒
protected final boolean tryReleaseShared(int releases) { for (;;) { int p = getState(); if (compareAndSetState(p, p + releases)) return true; } }
FutureTask则是在设置状态为RUN,并且唤醒阻塞的线程
void innerSet(V v) { for (;;) { int s = getState(); if (s == RAN) return; if (s == CANCELLED) { releaseShared(0); return; } if (compareAndSetState(s, RAN)) { result = v; releaseShared(0); done(); return; } } }
看了这么多典型的synchronizer可以发现,这种颜色标记出来的,是每种synchronizer可能不同的地方,需要子类自己实现;而这种颜色的,不管是什么synchronizer都是一样的,一个队列,就那么几个操作,比如入对挂起,单个唤醒出对列、全部唤醒出对列,所以它的代码在AQS里面,主要是一些队列和锁的操作,后面再抓个例子看看;需要自定义的主要有以下几个操作:
tryAcquire 返回独占模式下状态的判断, Reentrant根据返回决定是否阻塞线程,加入等待队列
tryRelease,返回独占模式下的状态判断,Reentrant判断是否成功释放锁,并且是否唤醒后面的等待线程
isHeldExclusively:返回独占模式下,当前线程是否占用了锁
tryAcquireShared,非独占模式下的状态判断,Latch判断state是否为0,根据返回决定是否阻塞当前线程,加入等待
tryReleaseShared,非独占模式下的状态判断,Latch将state减1状态,并且父类根据返回决定是否需要唤醒所有等待线程
tryAcquire,tryRelease和isHeldExclusively三个方法为需要独占形式获取的synchronizer实现的,而tryAcquireShared和tryReleasedShared为需要共享形式获取的synchronizer实现。(从我的角度来看,有tryAcquire和tryRelease就够了,可能是为了语义上更加明显吧)
Node的结构
队列中保存的Node元素结构是这样的:
节点的状态
状态的判定非常简单,不用判断特定的数值,非负数表示当前节点不必去发出通知;
SIGNAL状态表示该节点的后继节点需要被唤醒。这个状态的作用主要是告诉前置节点:“你结束以后,你后面还有兄弟需要被唤醒”,如果没有这个状态,那么unlock以后,仅仅是修改锁的状态,不会有什么操作!
如果因为中断/超时,该请求已经取消,会修改状态未Cancled,遇到取消的任务,那么需要踢出这些节点,并把后面的节点接上,Pre这个引用在一般的CLH锁中是没有的,这里主要是为了在取消的时候,保证取消节点的next可以指向取消节点的pre。
另外,
注意:依赖Node状态去判断是否有后继结点需要唤醒,又会牵涉到变量的竞争,为了避免竞争,必须用1.原子操作先设置Node状态,2.再次尝试获取锁,3.失败以后再阻塞线程!看下面shouldParkAfterFailedAcquire的例子
它是是一个双向链表:
+----------+ prev +--------+ +------+
| head | <------- | | <-------- | tail |
| | ------->| | --------à| |
+---------+ next +---------+ +------+
Head节点就是代表正在占用锁的节点,但是该链表是延迟初始化的,也就是说,并不会在第一次有代码获取锁的时候就初始化这个队列,只会在第一次真正有线程阻塞,需要加入阻塞队列的时候初始化!因此:
2.在有一个阻塞线程的时候是这样:
Dummy head----àwait thread1(tail)
2.两个阻塞线程的时候是这样:
Dummy head----àwait thread1----àwait thread 2(tail)
4.当前面一个线程结束的时候,会唤醒head后的第一个节点:
wait thread1(head)----àwait thread 2(tail)
LockSupport
另外,还必须了解下一个工具类,LockSupport
为阻塞线程提供基础的功能,它由一对park和unpark组成,park会阻塞当前线程,unpark“唤醒”等待线程;内部使用了类似信号量的“许可”机制,该许可为0,park会在许可等于0的时候下阻塞,等于1的时候立即返回,并且将许可减为0,umpark会尝试唤醒线程,并且将许可+1(最大值就是1)。因此,如果先调用unpark方法,再调用park是无效的,因为这时候许可为1,park会立即返回,搞段简单的代码测试下:
public static void main(String args[]) throws Exception { //先调用下unpark LockSupport.unpark(Thread.currentThread()); LockSupport.park(); //打出了not work,说明没起作用 System.out.println("not work"); LockSupport.park(); System.out.println(" wok"); }因此,从原则上来说,最好park/unpark按顺序,依次出现。
另外也并不是只有调用unpark方法才会返回,在下面3中情况下,park都会返回:
1.其他线程对当前线程调用了unpark方法,
2.其他线程中断了当前阻塞的线程
3.“不靠谱的”,毫无理由的返回,这类似一种“忙等待”的机制,不断地返回,不断地检查条件,不过这种方式自旋的时间更短一些,因为这个原因,需要向下面这样调用park方法:
while (!canProceed()) { ... LockSupport.park(this); }wait/notify是和对象、锁、等待队列、线程强关联的,在调用wait的时候,必须有锁,这时候释放对象上的锁,将当前线程加入该对象锁的等待队列;
park/unpark和当前对象、锁、等待队列无关,park方法只是会挂起当前线程;unpark(thread)方法唤醒对应的线程,至于是否有锁、是否放入对待队列,我们并不关心!下面的例子证明了park/wait他们之间是没什么关联的:
public class LockSupportAndWait { public static void main(String[] args) throws InterruptedException{ Thread t=new Thread(){ public void run(){ try { System.out.println("wait"); synchronized(this){ this.wait(); } System.out.println("notify work"); System.out.println("============================="); System.out.println("park"); //因为上面提到的原因,这里调用两次park LockSupport.park(); LockSupport.park(); System.out.println("unpark work"); } catch (InterruptedException ex) { } } }; t.start(); TimeUnit.MILLISECONDS.sleep(100); System.out.println("go to unpark"); LockSupport.unpark(t); System.out.println("go to notify"); goNotify(t); TimeUnit.MILLISECONDS.sleep(1000); System.out.println("go to notify"); goNotify(t); System.out.println("go to unpark"); LockSupport.unpark(t); } public static void goNotify(Thread t){ synchronized(t){ t.notify(); } } }结果(可以看到他们之间没有影响):
wait
go to unpark
go to notify
notify work
=============================
park
go to notify
go to unpark
unpark work
ps:下一篇再找个源码分析下吧,这篇长了点
发表评论
-
关于java线程(4)----JUC之 原子操作
2011-07-17 22:35 4088Java 理论与实践: 流行的原子 Java 理论与 ... -
AQS:ReentrantLock源码分析
2011-07-03 22:04 3026接着上一篇的 关于java线程(4)---- ... -
AQS:ReentrantLock源码分析
2011-07-03 20:28 0接着上一篇的 关于java线程(4)----JUC之 ... -
关于java线程(3)----JUC之 AQS 状态依赖的抽象
2011-07-02 15:27 0JUC之 AQS 状态依赖的 ... -
关于java线程(3)----JUC之 AQS 状态依赖的抽象
2011-06-30 00:41 7没写完,发点上来先,逼自己发,免得又不学习了!! JU ... -
关于java线程(3)--中断任务
2011-06-19 21:13 3002线程的状态四种: 1 ... -
关于java线程(2)----访问共享数据:竞争与协作
2011-06-18 19:27 3151A.共享对象 一个线程在它的生命周期内都只会访问它自己 ... -
关于java线程(1)
2011-06-17 00:26 1788关于线程基础 A. 并发在java中本身就是无处不在 ... -
我的理解--关于jmx
2010-05-28 16:58 50883JMX Java Management Extensions ...
相关推荐
内容概要:本文详细介绍了基于MATLAB GUI界面和卷积神经网络(CNN)的模糊车牌识别系统。该系统旨在解决现实中车牌因模糊不清导致识别困难的问题。文中阐述了整个流程的关键步骤,包括图像的模糊还原、灰度化、阈值化、边缘检测、孔洞填充、形态学操作、滤波操作、车牌定位、字符分割以及最终的字符识别。通过使用维纳滤波或最小二乘法约束滤波进行模糊还原,再利用CNN的强大特征提取能力完成字符分类。此外,还特别强调了MATLAB GUI界面的设计,使得用户能直观便捷地操作整个系统。 适合人群:对图像处理和深度学习感兴趣的科研人员、高校学生及从事相关领域的工程师。 使用场景及目标:适用于交通管理、智能停车场等领域,用于提升车牌识别的准确性和效率,特别是在面对模糊车牌时的表现。 其他说明:文中提供了部分关键代码片段作为参考,并对实验结果进行了详细的分析,展示了系统在不同环境下的表现情况及其潜在的应用前景。
嵌入式八股文面试题库资料知识宝典-计算机专业试题.zip
嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_3.zip
内容概要:本文深入探讨了一款额定功率为4kW的开关磁阻电机,详细介绍了其性能参数如额定功率、转速、效率、输出转矩和脉动率等。同时,文章还展示了利用RMxprt、Maxwell 2D和3D模型对该电机进行仿真的方法和技术,通过外电路分析进一步研究其电气性能和动态响应特性。最后,文章提供了基于RMxprt模型的MATLAB仿真代码示例,帮助读者理解电机的工作原理及其性能特点。 适合人群:从事电机设计、工业自动化领域的工程师和技术人员,尤其是对开关磁阻电机感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解开关磁阻电机特性和建模技术的研究人员,在新产品开发或现有产品改进时作为参考资料。 其他说明:文中提供的代码示例仅用于演示目的,实际操作时需根据所用软件的具体情况进行适当修改。
少儿编程scratch项目源代码文件案例素材-剑客冲刺.zip
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
内容概要:本文详细介绍了基于PID控制器的四象限直流电机速度驱动控制系统仿真模型及其永磁直流电机(PMDC)转速控制模型。首先阐述了PID控制器的工作原理,即通过对系统误差的比例、积分和微分运算来调整电机的驱动信号,从而实现转速的精确控制。接着讨论了如何利用PID控制器使有刷PMDC电机在四个象限中精确跟踪参考速度,并展示了仿真模型在应对快速负载扰动时的有效性和稳定性。最后,提供了Simulink仿真模型和详细的Word模型说明文档,帮助读者理解和调整PID控制器参数,以达到最佳控制效果。 适合人群:从事电力电子与电机控制领域的研究人员和技术人员,尤其是对四象限直流电机速度驱动控制系统感兴趣的读者。 使用场景及目标:适用于需要深入了解和掌握四象限直流电机速度驱动控制系统设计与实现的研究人员和技术人员。目标是在实际项目中能够运用PID控制器实现电机转速的精确控制,并提高系统的稳定性和抗干扰能力。 其他说明:文中引用了多篇相关领域的权威文献,确保了理论依据的可靠性和实用性。此外,提供的Simulink模型和Word文档有助于读者更好地理解和实践所介绍的内容。
嵌入式八股文面试题库资料知识宝典-2013年海康威视校园招聘嵌入式开发笔试题.zip
少儿编程scratch项目源代码文件案例素材-驾驶通关.zip
小区开放对周边道路通行能力影响的研究.pdf
内容概要:本文探讨了冷链物流车辆路径优化问题,特别是如何通过NSGA-2遗传算法和软硬时间窗策略来实现高效、环保和高客户满意度的路径规划。文中介绍了冷链物流的特点及其重要性,提出了软时间窗概念,允许一定的配送时间弹性,同时考虑碳排放成本,以达到绿色物流的目的。此外,还讨论了如何将客户满意度作为路径优化的重要评价标准之一。最后,通过一段简化的Python代码展示了遗传算法的应用。 适合人群:从事物流管理、冷链物流运营的专业人士,以及对遗传算法和路径优化感兴趣的科研人员和技术开发者。 使用场景及目标:适用于冷链物流企业,旨在优化配送路线,降低运营成本,减少碳排放,提升客户满意度。目标是帮助企业实现绿色、高效的物流配送系统。 其他说明:文中提供的代码仅为示意,实际应用需根据具体情况调整参数设置和模型构建。
少儿编程scratch项目源代码文件案例素材-恐怖矿井.zip
内容概要:本文详细介绍了基于STM32F030的无刷电机控制方案,重点在于高压FOC(磁场定向控制)技术和滑膜无感FOC的应用。该方案实现了过载、过欠压、堵转等多种保护机制,并提供了完整的源码、原理图和PCB设计。文中展示了关键代码片段,如滑膜观测器和电流环处理,以及保护机制的具体实现方法。此外,还提到了方案的移植要点和实际测试效果,确保系统的稳定性和高效性。 适合人群:嵌入式系统开发者、电机控制系统工程师、硬件工程师。 使用场景及目标:适用于需要高性能无刷电机控制的应用场景,如工业自动化设备、无人机、电动工具等。目标是提供一种成熟的、经过验证的无刷电机控制方案,帮助开发者快速实现并优化电机控制性能。 其他说明:提供的资料包括详细的原理图、PCB设计文件、源码及测试视频,方便开发者进行学习和应用。
基于有限体积法Godunov格式的管道泄漏检测模型研究.pdf
嵌入式八股文面试题库资料知识宝典-CC++笔试题-深圳有为(2019.2.28)1.zip
少儿编程scratch项目源代码文件案例素材-几何冲刺 V1.5.zip
Android系统开发_Linux内核配置_USB-HID设备模拟_通过root权限将Android设备转换为全功能USB键盘的项目实现_该项目需要内核支持configFS文件系统
C# WPF - LiveCharts Project
少儿编程scratch项目源代码文件案例素材-恐怖叉子 动画.zip
嵌入式八股文面试题库资料知识宝典-嵌⼊式⼯程师⾯试⾼频问题.zip