【多线程进阶】重要!!!
1.锁策略
一般基于特定的锁(synchronized)来说:
对于系统原生的锁(Linux 提供的 mutex 锁):
synchronized 的加锁过程,尤其是“自适应”是怎么回事?
当线程执行到 synchronized 时,如果这个对象当前处于未加锁的状态,就会经历以下过程:
1.偏向锁阶段(没有线程来竞争)
核心思想:“懒汉模式”,能不加锁就不加锁,能晚加锁就晚加锁。所谓偏向锁,并非真的加锁了,而只是做了一个非常轻量的标记。
偏向锁标记是对象头里的一个属性。每个锁对象都有自己的这个标记,当这个锁对象首次被加锁时,先进入偏向锁。如果这个过程中,没有涉及到锁竞争,下次加锁还是先进入偏向锁。一旦这个过程中升级成轻量级锁了,后续再针对这个对象加锁,都是轻量级锁(跳过了偏向锁了)
举个例子:
搞暧昧就是偏向锁!只是做一个标记,没有真加锁(也不会互斥),一旦有其他线程来竞争这个锁,就在另一个线程之前,先把锁获取到。
从偏向锁就会升级到轻量级锁(真加锁了,就有互斥了)
如果搞暧昧的过程中,要是没人来竞争,整个过程就把加锁这样的操作就完全省略了!!!
假设有个女生(漂亮 & 有才华 & 有心机 & 擅长时间管理),谈了一个男朋友,一段时间后,厌烦了,想换一个,效率是比较低的,这时有两个阶段:
1.想办法和这个男朋友分手(各种作,先把他作烦了,再提分手)
2.和下一个培养感情(这是优化策略,引入“备胎池”)
如何把第一阶段能优化一下?
当最开始和这个男生谈恋爱时,就不和他确认关系,有情侣之实,但无情侣之名,每天在一起干的事情都是情侣之间的事情(暧昧),当男生提及说:我们是什么关系?女生笑而不语,或者把话题岔开。如果有一天厌倦了,直接把他拉黑,踹一边就可以了,这时再纠缠,就来一句:“我只是把你当作正常的普通朋友”,让他闭嘴。
当和这个男生搞暧昧时,如果发现有其他女生也在试图接近男生,这时,就立即和他确认关系,并且让其他女生离他远点,一切尽在掌握,如此便加上锁了!
非必要不加锁!在遇到竞争的情况下,偏向锁没有提高效率,但是如果在没有竞争的情况下,偏向锁就大幅度提高了效率!
2.轻量级锁阶段(有竞争但不多)
此处是通过自旋锁方式来实现的
优势:另外的线程把锁释放了,就会第一时间拿到锁
偏向锁 -> 轻量级锁 这个过程不涉及到解锁,能够确保持有偏向锁状态的线程肯定是能先拿到锁的
劣势:比较消耗CPU
与此同时,synchronized 内部也会统计,当前这个锁对象上,有多少个线程在参与竞争这里,当发现参与竞争的线程比较多了,就会进一步升级到重量级锁
对于自旋锁来说,如果同一个锁竞争者很多,大量线程都在自旋,整体CPU 的消耗就很大了。
3.重量级锁阶段
此时拿不到锁的线程就不会继续自旋了,而是进入“阻塞等待”,就会让出CPU了(不会使CPU占用率太高)当前线程释放锁时,就由系统随机唤醒一个线程来获取锁了
此处锁只能升级不能降级,自适应这个词严格说不算严谨,不可靠,保不齐未来某个版本就能降级了。
2.锁消除
synchronized 中内置的优化策略
3.锁粗化
举个例子:
进入公司后,领导安排一些工作,当把工作做完之后,一定要及时给领导汇报结果!!!
好的做法是当面汇报,再不济电话汇报,最差写邮件汇报。
电话汇报时,领导接了电话,相当于被加锁了,最好一口气把所有要汇报的工作说完,别挂了电话再打第二个,除了让领导觉得烦之外,第二个电话,第三个电话能顺利打通吗?不一定,此时消耗的总时间就更多了!
汇报工作类似于 git commit 和 git push,不进行 push,服务器这边感知不到你进行的修改。
小结:
4.CAS (compare and swap)
如果发现寄存器和内存的两个值不一样,说明被改过,就从内存中取别的线程修改过的值,在寄存器里改。
原子类是把指令捆绑,但是 volatile 只能把指令禁止重排序,volatile 修饰的变量只能保证按照指令顺序执行,但是不能保证执行的过程中没有指令插入。