1.Lockçawait/singal å Objectçwait/notify çåºå«
2.你应该知道的源码wait/notify那点事儿
3.vue2.x中的数据异步更新和nextTick方法解析
4.Vue原理依赖更新 - 源码版
5.UE4AnimNotify 相关源码分析
6.Vue—关于响应式(二、异步更新队列原理分析)
Lockçawait/singal å Objectçwait/notify çåºå«
Lockçawait/singal å Objectçwait/notify çåºå«
å¨ä½¿ç¨Lockä¹åï¼æ们é½ä½¿ç¨Object çwaitånotifyå®ç°åæ¥çã举ä¾æ¥è¯´ï¼ä¸ä¸ªproduceråconsumerï¼consumeråç°æ²¡æä¸è¥¿äºï¼çå¾ ï¼produerçæä¸è¥¿äºï¼å¤éã
线ç¨consumer 线ç¨producer
synchronize(obj){
obj.wait();//没ä¸è¥¿äºï¼çå¾
} synchronize(obj){
obj.notify();//æä¸è¥¿äºï¼å¤é
}
æäºlockåï¼ä¸éåäºï¼ç°å¨æ¯ï¼
lock.lock();
condition.await();
lock.unlock(); lock.lock();
condition.signal();
lock.unlock();
为äºçªåºåºå«ï¼çç¥äºè¥å¹²ç»èãåºå«æä¸ç¹ï¼
1. lockä¸åç¨synchronizeæåæ¥ä»£ç å è£ èµ·æ¥ï¼
2. é»å¡éè¦å¦å¤ä¸ä¸ªå¯¹è±¡conditionï¼
3. åæ¥åå¤éç对象æ¯conditionèä¸æ¯lockï¼å¯¹åºçæ¹æ³æ¯awaitåsignalï¼èä¸æ¯waitånotifyã
为
ä»ä¹éè¦ä½¿ç¨conditionå¢ï¼ç®åä¸å¥è¯ï¼lockæ´çµæ´»ã以åçæ¹å¼åªè½æä¸ä¸ªçå¾ éåï¼å¨å®é åºç¨æ¶å¯è½éè¦å¤ä¸ªï¼æ¯å¦è¯»ååã为äºè¿ä¸ªçµæ´»
æ§ï¼lockå°åæ¥äºæ¥æ§å¶åçå¾ éåå离å¼æ¥ï¼äºæ¥ä¿è¯å¨æ个æ¶å»åªæä¸ä¸ªçº¿ç¨è®¿é®ä¸´çåºï¼lockèªå·±å®æï¼ï¼çå¾ éåè´è´£ä¿å被é»å¡ç线ç¨
ï¼conditionå®æï¼ã
éè¿æ¥çReentrantLockçæºä»£ç åç°ï¼conditionå ¶å®æ¯çå¾ éåçä¸ä¸ªç®¡çè ï¼conditionç¡®ä¿é»å¡ç对象æ顺åºè¢«å¤éã
å¨Lockçå®ç°ä¸ï¼LockSupport被ç¨æ¥å®ç°çº¿ç¨ç¶æçæ¹åï¼åç»å°æ´è¿ä¸æ¥ç 究LockSupportçå®ç°æºå¶ã
你应该知道的源码wait/notify那点事儿
Java的Object类中的wait()和notify()方法在多线程协作中起着关键作用,它们控制着线程间的源码等待、唤醒和切换。源码首先,源码了解线程的源码酷信源码精益求精六种状态:新建、就绪、源码运行、源码阻塞、源码完成。源码接着,源码看一个代码示例:
看似平凡的源码代码,却隐藏着问题。源码当不正确使用synchronized时,源码wait()和notify()可能会导致异常。源码这是因为wait()需要在同步代码块中调用,以保证线程间的通信原子性,避免被中断。
当thread2调用wait后,如果thread1不释放锁,其他线程无法进入同步块。wait会释放锁,但唤醒后会重新获取,确保线程在被唤醒后继续执行。从JVM源码看,wait会放弃锁然后等待唤醒,notify则会选择一个线程唤醒,并尝试获取锁。
wait()可能会抛出InterruptedException,因为当其他线程调用interrupt()时,wait会在恢复时检查并抛出异常。调用notify()后,线程并不会立即执行,而是根据JVM的默认策略在同步代码块结束时唤醒。
至于性能影响,wait和notify使用park/unpark机制,不占用CPU,不影响系统性能。而监视器(Monitor)是每个对象的核心,控制着线程对对象的访问。进入区、拥有者和等待区的概念解释了线程如何在对象锁的控制下交互。
最后,要注意的scala的filter源码是,Thread.sleep()方法会让线程休眠,但不释放监视器,这点与wait和notify不同。
vue2.x中的数据异步更新和nextTick方法解析
前言
众所周知,vue中的更新时异步的,比如this.msg=xxx,你看起来他是立马更新了,其实并没有。它会异步执行,接下来就来看看怎么实现的吧。
先上图首先从数据改动开始说起调用this.msg=xxx数据发生变更
在数据初始化阶段已经收集了依赖的watcher到dep中,执行dep.notify通知watcehr变更
notify方法遍历调用所有以来的watcher的update方法,把当前watcher实例放入queueWatcher函数中执行,接下来就是异步更新的关键了,看代码
queueWatcher函数代码在src\core\observer\scheduler.js主要作用:把当前watcher实例添加到一个queue中
exportfunctionqueueWatcher(watcher:Watcher){ //拿到watcher的唯一标识constid=watcher.id//无论有多少数据更新,相同的watcher只被压入一次//我理解这就是为什么在一次操作中,多次更改了变量的值,但是只进行了一次页面更新的原因,//同一变量依赖它的watcher是一定的,所以已经存在了就不再放进watcher队列中了,也不会走后面的逻辑if(has[id]==null){ //缓存当前的watcher的标识,用于判断是否重复has[id]=true//如果当前不是刷新状态,直接入队if(!flushing){ queue.push(watcher)}else{ //ifalreadyflushing,splicethewatcherbasedonitsid//ifalreadypastitsid,itwillberunnextimmediately.//此处能走到这儿,说明flushSchedulerQueue函数被执行了watcher队列已经正在开始被更新了,//并且在执行某个watcher.run方法的时候又触发的数据响应式更新,重新触发了queueWatcher//因为在执行的时候回有一个给watcher排序的操作,所以,当watcher正在更新时已经是排好顺序了的,此时需要插入到特定的位置,保持watcher队列依然是保持顺序的leti=queue.length-1while(i>index&&queue[i].id>watcher.id){ i--}queue.splice(i+1,0,watcher)}//queuetheflush//waiting表示当前的flushSchedulerQueue还没有被执行,因为还没有重置状态,waiting仍然为true//所以waiting的意义就是表明是否执行了flushSchedulerQueue,if(!waiting){ waiting=true//直接同步刷新队列if(process.env.NODE_ENV!=='production'&&!config.async){ //同步执行flushSchedulerQueue()return}//把更新队列函数放到异步队列中nextTick(flushSchedulerQueue)}}}flushSchedulerQueue代码在相同目录下//主要作用:遍历执行每一个watcher的run方法,进而实现数据和视图的更新,并在执行完所有的方法之后,重置状态,表示正在刷新队列的flushing,表示watcher是否存在的has,表示是否需要执行nexttick的waiting
functionflushSchedulerQueue(){ //当方法被执行时,设置为正在刷新状态,以示可以继续执行nextTick方法flushing=true//把队列中的watcher排个序,/***排序的作用:(此句照搬照抄而来)*1.保证父组件的watcher比子组件的watcher先更新,因为父组件总是先被创建,子组件后被创建*2.组件用户的watcher在其渲染watcher之前执行。*3.如果一个组件在其父组件执行期间被销毁了,会跳过该子组件。*/queue.sort((a,php源码如何授权b)=>a.id-b.id)//中间略去若干代码...//遍历queue中存的所有的watcher,执行run方法更新for(index=0;index<queue.length;index++){ watcher=queue[index]watcher.run()}//因为queue是在一个闭包中,所以当遍历执行完毕了,就把队列清空queue.length=0;//has是判断当前watcher是否重复,作为是否把watcher放进queue的依据//此时已经执行完了queue中的所有watcher了,之前已经执行过的watcher如果发生了变更,可以重新加入了has={ }//waiting是判断是否执行nextTick的标识,当前的刷新队列已经执行完毕了,说以,可以设置为false了,执行下一轮的的添加异步事件队列的方法//flushing是判断是否当前异步事件正在执行的标志,当前更新完毕,作为判断watcher入队的形式waiting=flushing=false}nextTick方法源码src\core\util\next-tick.js
exportfunctionnextTick(cb?:Function,ctx?:Object){ let_resolve//把执行更新操作之后的回调函数添加到队列里//用trycatch包装一下传进来的函数,避免使用$nextTick时,传入的回调函数出错能够及时的捕获到//只要执行了nextTick函数,就把回调函数添加到回调列表里//这里的cb回调函数就是flushSchedulerQueue函数,里面执行了queue中存放的所有的watcher.run方法callbacks.push(()=>{ if(cb){ try{ cb.call(ctx)}catch(e){ handleError(e,ctx,'nextTick')}}elseif(_resolve){ _resolve(ctx)}})//通过pending来判断是否需要向任务队列中添加任务//如果上一个清空回调列表的当flushCallbacks函数还在任务队列中,就不往任务队列中添加//第一次执行时,就默认就添加一个进任务队列,一旦添加进任务队列,就表明暂时不在需要往任务队列中添加flush函数//当执行了上一个flushCallbacks函数的时候,pending修改为false,表明可以重新添加一个清空回调列表的flush函数到任务队列了if(!pending){ pending=true//这里是调用清空callbacks数组中方法,并执行的函数,timerFunc()}//$flow-disable-line//判断当前环境是否支持promise,如果支持的话,可以返回一个期约对象,if(!cb&&typeofPromise!=='undefined'){ returnnewPromise(resolve=>{ _resolve=resolve})}}timerFunc()方法,主要是做一些降级操作,实现异步的关键
timerFunc=()=>{ Promise.resolve().then(flushCallbacks)}//如果当前环境不支持的话,会进行一定的降级操作,直到最后,用宏任务settimeout来处理看看flushCallbacks,任务就是执行了所有的callbacks函数
functionflushCallbacks(){ //如果开始执行了flushCallbacks说明,当前的异步任务已经为空了,如果此时再nextTick方法会添加新的任务进去了pending=false//拷贝一份callbacks中的所有回调函数,用于执行constcopies=callbacks.slice(0)//随即删除所有callbackscallbacks.length=0//当微任务队列中的flushCallbacks添加到执行栈中了,就执行callbacks中的所有的函数//也就是调用执行每一个flushSchedulerQueue函数,然后遍历执行每一个函数for(leti=0;i<copies.length;i++){ copies[i]()}}基本关键变量的作用waiting:变量,作为是否执行nextTick,添加flushSchedulerQueue方法的关键,标志着callbacks中是否有flushSchedulerQueue方法,比如同一个变量的改变,可能会影响多个watcher,因为执行flushSchedulerQueue是异步的,遍历dep.update先把所有的应用中心源码下载watcher都放入到queue中,也才只执行了一次nextTick,callbacks中也只有一个方法。虽然当第一次方如watcher时就会执行nexttick把flushSchedulerQueue方法放入callbacks中,看起来好像已经要执行了,但是因为queue是闭包变量,所以,后续的变量仍然可以添加queue中,
flushing::表示是否正在执行flushSchedulerQueue方法,如果是正在执行更新方法的话,对向已经排好序的watcher队列中添加新的watcher,需要把新watcher插入到排好序的指定的位置,这也就是为什么遍历watdher那块儿会直接使用queue.length的原因,这个长度会发生变化。
pending::pending是决定是否把更新callbacks数组的方法放入异步队列的关键,保证了异步队列中只有一个清空callbacks的任务,也就解释了,连续手动执行多个$nextTick方法不会立即执行,也还是会把他们的回调放入callbacks中,然后等到任务都执行完毕了,一下把所有的回调函数都执行掉。
参考
vue源码
/post/
Vue原理依赖更新 - 源码版
本文深入剖析Vue源码中的依赖更新机制,带你从源码层面理解这一关键概念。依赖更新是响应式系统中不可或缺的一环,它确保了数据变化时视图的及时响应。理解依赖更新,需要从依赖收集的背景出发,掌握其核心逻辑。
依赖收集是响应式系统中数据变化追踪的基础,它使得Vue能够在数据变动时,自动更新相关视图。此过程涉及基本数据类型和引用数据类型的收集,为依赖更新奠定了基础。
依赖更新的核心操作是调用`Object.defineProperty`的`set`函数。当数据值发生改变时,`set`函数被触发,从而触发依赖更新。这一步骤是依赖更新的关键,实现了数据变化与视图更新之间的联动。
依赖更新的精髓在于通知机制。这一机制通过`dep.notify`函数实现,负责遍历依赖存储器,并调用`watcher.update`方法,以此触发视图的更新。`dep`是devexpress18源码依赖存储器的核心,存储了所有与数据变化相关的监视器(`watcher`)。
了解`dep`和`watcher`的交互是理解依赖更新的关键。`dep`负责收集依赖,而`watcher`则在数据变化时触发视图更新。当数据变化触发`dep.notify`时,`watcher.update`方法被调用,执行预设的更新函数。这个过程涉及数据的重新读取、DOM节点的生成与插入,实现了视图的即时响应。
从Vue实例创建到初始化,再到挂载页面,整个流程中`watcher`的更新函数起到了关键作用。这个函数通常包含了视图更新的具体逻辑,如调用渲染函数生成DOM节点。虽然涉及的源码较多,但核心在于重新生成DOM节点,确保页面在数据变化时能够实时更新。
依赖更新的流程简而言之,包括直接调用`watcher.update`、执行渲染函数以生成DOM节点、以及更新DOM节点以完成页面更新。这一机制确保了Vue应用在数据变化时的高效响应,使得用户体验更加流畅。
理解Vue依赖更新不仅有助于深入掌握Vue源码,还能提升开发者在实际项目中的应对能力,特别是在复杂应用中处理数据变化与视图更新的关系。通过细致分析Vue源码,可以更加清晰地认识到这一机制在实际应用中的实现细节与优化空间。
如有任何描述不当或疑问,欢迎在后台联系作者,共同探讨Vue响应式系统中的依赖更新机制。
UE4AnimNotify 相关源码分析
深入解析UE4的动画通知机制:揭秘AnimNotify与AnimNotifyState的协作舞蹈动画通知的起舞序列</
在UE4的动画世界里,每帧的Tick函数是核心舞者。首先,AnimNotify</优雅地起舞,接着是Tick Pose的轻盈转身,然后是骨矩阵的更新与FinalizeBoneTransform的深情凝视,这是处理Notify/Event Handling的关键环节。而在ConditionallyDispatchQueuedAnimEvents中,AnimNotify和Montage的结束篇章被巧妙触发。Tick的华丽编舞</
Tick的步骤如下:AnimNotify</(即启)→ Tick Pose(轻盈步伐)→ 更新骨矩阵(RefreshBoneTransforms)→ FinalizeBoneTransform(情感升华)→ 释放AnimNotifyEvent的绚丽尾声。通知处理的细微转折</
在UAnimInstance::TriggerAnimNotifies的舞台上,每个新加入的动画通知(AnimNotifyState)都会被逐一审视,可能延后'NotifyBegin'的时机。同时,旧的AnimNotifyState会在触发'NotifyEnd'后优雅谢幕。新状态的'NotifyBegin'随之登场,而'NotifyTick'则在活跃状态下悄然进行。重要的是,尽管'NotifyEnd'总在'NotifyBegin'之前,但可能因帧率变化而稍显滞后。意外的节奏混乱</
帧率的波动可能导致微妙的混乱,例如,当从帧到帧,'NotifyEnd'可能会延迟到下一帧才奏响,尽管时间跨度看似短暂。比如,当检测到Projectile_0消失时,尽管它在第六帧才真正结束,但'NotifyEnd'却可能在第五帧后才触发,使得动画逻辑出现短暂的不协调。状态转换的精准切换</
以SpawnProjectile_0和SpawnProjectile_1为例,Begin阶段的切换精准有序:新状态在检测到新出现的Projectile_1时启动,而当旧状态的Projectile_0消失时,'NotifyEnd'才宣告其结束。从2到2.5帧的过渡,动画队列如丝般流畅地从SpawnProjectile_0切换到SpawnProjectile_1,确保了逻辑的连贯性。探索更深层次的机制</
要深入了解动画通知的奥秘,记得查阅官方文档Animation Notifications (Notifies),如果你是一位热衷于开发的舞者,不妨通过邮件gaoyuan.bob@bytedance.com或投递简历链接,加入我们的舞蹈团队,共同探索更精彩的动画世界。
Vue—关于响应式(二、异步更新队列原理分析)
本节学习要点:Event Loop、Promise
关于Event Loop的介绍,可以参考阮一峰老师的文章。
关于Promise,请访问:developer.mozilla.org/z...
上一节介绍了Vue通过Object.defineProperty拦截数据变化的响应式原理,数据变化后会触发notify方法来通知变更。这一节将继续分析,收到通知后Vue会开启一个异步更新队列。
以下是两个问题:
一、异步更新队列
首先看一段代码演示。
将上一节的代码拿过来,假设我们现在不仅依赖x,还有y、z,分别将x、y、z输出到页面上。我们现在依赖了x、y、z三个变量,那么我们应该把onXChange函数名改为watch,表示它可以监听变化,而不仅仅是监听一个x的变化。
可以看到这三个值都被打印在页面上。
现在我们对x、y、z的value进行修改。
查看页面,结果没有问题,每个数据的变化都被监听到并且进行了响应。
既然结果是对的,那我们的问题是什么?
这个问题是:每次数据变化都进行了响应,每次都渲染了模板,如果数据变化了一百次、一千次呢?难道要重复渲染一百遍、一千遍吗?
我们都知道频繁操作DOM会影响网页性能,涉及重排和重绘的知识感兴趣请阅读阮一峰老师的文章:ruanyifeng.com/blog/...
因此,既要保证所有的依赖都准确更新,又要保证不能频繁渲染成为了首要问题。现在我们修改x.value、y.value、z.value都是同步通知依赖进行更新的,有没有一种机制可以等到我修改这些值之后再执行更新任务呢?
这个答案是——异步。
异步任务会等到同步任务清空后执行,借助这个特点和我们前面的分析,我们需要:
按照步骤,我们创建如下代码:
接着我们需要修改一下notify的代码,监听到数据变化后不立即调用依赖进行更新,而是将依赖添加到队列中。
回到页面,我们发现页面上还是重复渲染了三次模板。
那么我们写的这段代码有什么用呢?异步又体现在哪里呢?接着往下看。
二、nextTick原理分析
上面的代码中,虽然我们开启了一个队列,并且成功将任务推入队列中进行执行,但本质上还是同步推入和执行的。我们要让它变成异步队列。
于是到了Promise发挥作用的时候了。关于宏任务和微任务的介绍请参考:zhuanlan.zhihu.com/p/...
我们创建nextTick函数,nextTick接收一个回调函数,返回一个状态为fulfilled的Promise,并将回调函数传给then方法。
然后只需要在添加任务时调用nextTick,将执行任务的flushJobs函数传给nextTick即可。
回到页面。
虽然修改了x、y、z三个变量的value,最后页面上只渲染了一次。
再来总结一下这段代码的执行过程:
这也正是Vue采用的解决方案——异步更新队列,官方文档描述得很清楚。
文档地址:cn.vuejs.org/v2/guide/r...
三、结合Vue源码来看nextTick
在Vue中,我们可以通过两种方式来调用nextTick:
(至于什么时候使用nextTick,如果你不偷懒看了官方文档的话,都能找到答案哈哈)
以下源码节选自vue2.6.版本,这两个API分别在initGlobalAPI函数和renderMixin函数中挂载,它们都引用了nextTick函数。
nextTick源码如下:
在内部,它访问了外部的callbacks,这个callbacks就是前面提到的队列,nextTick一调用就给队列push一个回调函数,然后判断pending(pending的作用就是控制同一时间内只执行一次timerFunc),调用timerFunc(),最后返回了一个Promise(使用过nextTick的应该都知道吧)。
我们来看一下callbacks、pending、timerFunc是如何定义的。
可以看到timerFunc函数只是调用了p.then方法并将flushCallbacks函数推入了微任务队列,而p是一个fulfilled状态的Promise,与我们自己的nextTick功能一致。
这个flushCallbacks函数又干了什么呢?
flushCallbacks中重新将pending置为初始值,复制callbacks队列中的任务后将队列清空,然后依次执行复制的任务,与我们自己的flushJobs函数功能一致。
看完上面的源码,可以总结出Vue是这么做的,又到了小学语文之——提炼中心思想的时候了。
对比一下我们自己写的代码,你学会了吗?
以上演示代码已上传github:github.com/Mr-Jemp/VueS...
后面要学习的内容在这里:
Vue—关于响应式(三、Diff Patch原理分析)
Vue—关于响应式(四、深入学习Vue响应式源码)
本文由博客一文多发平台OpenWrite发布!
RKs Android 屏蔽蓝牙弹窗及桌面悬浮通知
针对RKs设备的Android系统,客户提出了一些特定需求的修改。首先,针对屏幕上的WebView,允许系统应用正常使用,改动位于
frameworks\base\core\java\android\webkit\WebViewFactory.java
文件中。
为了屏蔽蓝牙配对弹出窗口,实现静默确认配对,改动涉及到
packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
当请求开启蓝牙时,自动确认设置在
packages\apps\Settings\res\values\config.xml
,将auto_confirm_bluetooth_activation_dialog设为true。
对于全局通知,尤其是桌面悬浮提示,需要在
frameworks\base\packages\SettingsProvider\res\values\defaults.xml
中修改,将def_heads_up_enabled设为0,如果不存在则新增该设置。
SystemUI的桌面音量面板弹出也被要求隐藏,改动在
frameworks\base\packages\SystemUI\src\com\android\systemui\volume\VolumeDialogImpl.java
中,注释掉mDialog.show()方法。
此外,为了优化用户体验,蓝牙外设连接成功后,Activity不再自动刷新,改动在
frameworks/base/core/java/android/app/ActivityThread.java
中。
最后,为了避免WIFI连接失败时弹出通知打扰用户,需要在
packages\modules\Wifi\service\java\com\android\server\wifi\ConnectionFailureNotifier.java
中屏蔽mNotificationManager.notify这一行代码。
以上就是针对客户特定需求所做的源码修改记录。
synchronize底层原理
synchronize底层原理是什么?我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:
1 package com.paddx.test.concurrent;
2
3 public class SynchronizedDemo {
4 public void method() {
5 synchronized (this) {
6 System.out.println(Method 1 start);
7 }
8 }
9 }
反编译结果:
关于这两条指令的作用,我们直接参考JVM规范中描述:
monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the thread blocks until the monitors entry count is zero, then tries again to gain ownership.
这段话的大概意思为:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
这段话的大概意思为:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
我们再来看一下同步方法的反编译结果:
源代码:
1 package com.paddx.test.concurrent;
2
3 public class SynchronizedMethod {
4 public synchronized void method() {
5 System.out.println(Hello World!);
6 }
7 }
反编译结果:
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。