java-synchronized层次

java 专栏收录该内容
31 篇文章 0 订阅

缺点什么

思考还是吃饭,这是一个问题。

一个坑位一个人,的确不能你拉我身上,我拉你兜里。

对于存在多个独立的操作者对公共资源的操作的问题,为了维持秩序,也为了公共财产的安全,应该需要那么一种东西来保证

对于公共资源,每次只能够有唯一一个操作者操作

这时候,就应该有一个凭证,它让拥有的操作,未拥有的等待;

贴切一点,就把它称为了锁,有了锁就拥有了操作权,想要操作,就需要先获取锁。

阴阳颠倒

对于多锁导致的死锁问题这里不赘述,主要讨论锁和资源的主次地位的变化。

锁怎么来的,大概是这样了;更混乱的问题来了,什么是公共资源,你咋知道这是公共资源。

资源内容

公共资源的定义不太清晰,但是参考一下它的特性,我们可以把它梳理出来

  • 每次操作只能有一个操作者
  • 操作前后具有事务性
    • 原子性:操作全部成功或者全部撤销
    • 一致性:每次只允许一个操作,天然保证
    • 隔离性:每次只允许一个操作,天然保证
    • 持久性:操作完成,结果记录不可销毁,也是一致性保证

这样一来,就会发现资源并非是一个简单的数据结构,只要满足上述条件,它可以是

  • 数据结构
  • 代码片段
  • 操作集合

因此,我们常用的synchronized的关键字,并不限定修饰一个变量属性,还可以针对方法和代码块。

因为java不支持元编程,要不说不定可以修饰class

资源定义

内容弄明白了,接下来是真正的矛盾点了:什么样的资源称得上公共

当然了,存在复数独立操作者准备同时操作的资源算是公共资源。

  • 如果是公共资源,我们应该XX安排每个独立操作者XX操作
  • 如果存在复数独立操作者,我们把它叫做公共资源

仔细品味,如果公共资源依赖于操作者的数量的话,那它就在独有资源和公共资源之间摇摆不定了。

这明显不是我们所期待的,真正的公共资源是可以、可能存在复数操作者同时操作的资源。

是否公共资源,并非取决于某时某刻的操作者的数量,所谓公共,应该是指定资源的一个特质。

公共声明

那对于公共资源应该怎样去标识它的公共呢?

没错,锁是结果,也是起因。

它算作公共资源的标识,也作为并发操作时候的处理解决方案。

但是,作为解决方案,它是固化的,是严格的,是底层的;但是对于所谓标识,它是人为的进行标识的。

它是并发操作时候的一种管理能力,通过人为的标识进行赋能而已。

它具备并发操作时候的安全调度管理,不过还需要回顾最本质的一个命题

存在安全的并发调度管理的资源,不一定存在并发操作。

上述命题是最本质、最核心的总结,而我们对于锁的使用,则是针对这种并发操作时候的并发管理策略。

基础背景

CPU执行

cpu是一个任务执行器,全部的任务都是由它来完成;每个cpu在某一时刻,只能够执行一个任务。

并且,这个任务并不保证能够执行完成,当它切换的时候,会保存当前的运行状态以便于下次恢复。

这个执行时间,由操作系统调度。

这就是cpu的分时策略,每个所谓的任务,其实是一条线程,内部可能由更多的任务。

每个线程,都需要抢占时间片,相当于cpu资源的锁,不过这个锁是有时间限制的。


不一定需要执行完毕才退出cpu,线程也能够主动的放弃,但是对于任务环境的切换,上下文的存储和加载,必然产生消耗。

内核状态

JVM毕竟是VM,它的基础功能并非由自身完全掌控。

我们使用的synchronized,最深层的实现并非是由JVM自己实现的,而是关联上操作系统底层,交给操作系统进行管理的。


有意思的是,以前遥不可及的多用户操作系统现在随处可见,反而是单用户的机器销声匿迹了。

硬件当然至此一份,cpu调度多任务的功能保证的是任务切换,但是多用户多任务怎么进行了。

核心仍然是多任务的调度执行,但是为了隔离每个用户,这些任务必然是需要进行隔离划分的,需要将任务分别进行存放。

也就是我们常说的用户空间。

内核空间做得是最核心的任务执行和调度,而用户空间是被隔离出来的每个用户的活动范围。

不过有些东西,涉及到系统内调用,这部分任务不得不交给内核空间。

这下降又上升的过程,消耗实在厉害。

深层的synchronized,就是进行的系统调用,会下沉到内核空间,严重耗时。

管理策略

一个最无奈的问题来了,一个可以被并发操作的公共资源,应该具备什么样的管理能力。

这个问题本身就是一个坑,因为所谓的管理能力并不应该是一成不变的(不是不能),应该针对具体的情况而定。

回归核心:具备并发管理的公共资源,是否一定需要进行管理。

正确回答:只有面临并发操作的公共资源,才需要进行特定的并发管理,针对性策略效果更好

而不同场景下,就出现了不同的管理策略,而这些管理策略对外统称都是,因此诞生了各种锁的称谓。

锁并不是一个东西,也不是一个标识,它是一个并发管理策略。

不是因为它是什么锁进行什么样子的管理,而是针对一种场景,诞生一种策略,称谓才是最后出现的最不重要的代称。

无锁

针对场景:没有声明锁。

没有锁的情况,完全不需要任何管理。

自旋锁

针对场景:存在多个并发操作者

让不满足条件的线程自旋,通过占用cpu来减少任务切换的开销。

  • 优点
    • 降低任务切换开销
  • 缺点
    • 短时间无法获取锁,会一直占用cpu资源进行无谓等待,造成浪费

因此,自旋总需要进行一定时间,或者自旋次数的限制,避免空耗cpu

这种办法常用在业务上层,需要做好万全策略。

偏向锁

针对场景:经常由一个操作者操作

这种情况下,简略的可以表述为

  • 环境:并发
  • 操作:唯一

就相当于只有一个独立资源操作者进行操作,都成为了个人资源了。

这个时候,不需要cas,直接将线程编号写入锁对象头的mark word,标识为私家资源。

后续都不存在所谓竞争,刷脸就直接开门。

轻量级锁

针对场景:简单并发,轻微冲突

偏向锁是家奴,但是存在更多的竞争者的时候,怎么办。

这个时候,线程都讲锁对象头的mark word拷贝到自己的lock record(位于栈帧)。

然后不断的cas进行状态检查,当检查到自己状态适合,马上将对象更新为自己,抢占锁,就轮到自己执行了。

重量级锁

针对场景:并发量大,冲突严重

这个时候冲突十分严重,口头上已经无法进行协调了,都快打架了,该找警察了。

这时候的就交给底层操作系统进行管理,由于会进行内核调用,陷入内核空间,引起更多的耗时。

前面所谓的协调,就是想办法避免并发竞争,但是竞争无法避免的时候,只能通过外部进行协调了。

策略切换

  1. 自旋锁

    • 业务上层自定义自旋或者系统自旋,达到一定次数或者时间线程进行wait
    • 自旋的默认的前提是存在并发冲突,因此和偏向锁无递进关系
    • 自旋失败后需要陷入的wait状态,否则会空耗CPU
    • 当并发竞争激烈时,应该替换为更高级的锁,避免CPU空转引起更大的消耗
    • 自旋过程中,多CPU不一定切换线程,降低开销。
  2. 偏向锁

    • 自家人,自家事,不存在竞争
    • 独立线程,或者并发时单条线程总是抢占到资源,体现为单线程操作,视作无竞争
    • 当出现更多竞争者的时候,清除偏向标记,进行到轻量级锁的升级
  3. 轻量级锁

    • 使用cas
    • jvm级别管理
    • 此时虽然采用的cas,但是属于JVM级别的线程管理
    • 虽然基于OS提供的能力,但是基础组件在用户空间可直接调用
    • 自旋不一定切换线程,降低开销
  4. 重量级锁

    • 系统管理
    • 内和空间
    • 存在系统调用,陷入内核空间,切换耗时

内容小结

仅此而已

这里只是从思想的层面贯穿所谓锁的理解,具体施行过程还需要参考更严谨的文章。

神经一问

为什么一定要进行重量锁的升级呢,一直cas不行么。

所谓蚁多咬死象,线程的切换和内核空间的切换虽然存在一定的耗时差异。

或者说本质还是CPU的任务切换,后一种只是不同空间下存在的额外的转换操作带来的操作耗时。

但是这部分耗时是有限的。


如果不升级锁的话,大量的活跃线程空转会导致CPU资源耗尽,达到一定的程度后,剩余资源都不足以支撑进行空间切换。

这个时候就会引发系统崩溃,无限的生产而有限的消费,最终就会破坏整个状态的平衡。


假设存在三个角色

  • 食客:可以进食,也不易不进食;相当于CPU
  • 服务员:服务食客;相当于整个管理体系
  • 厨师:产出食品,天生的欲望驱使他将食物给食客进食;类比于线程任务

当厨师较少的情况下,即使食客有点撑,但是依旧能够慢慢消化全部厨师出产的全部食品。

但是厨师过多的情况下,他们就堵在了餐桌旁边等待送餐,不过食客进食速度不够,然后整个屋子挤满了厨师。

如果缺乏服务员的调度,大概就是这种情形了,如果服务员发现大厅快满了,让部分厨师先睡觉,问题就迎刃而解。

要是大厅拥挤得服务员都无法入内,那这家餐馆就只能崩坏,等待下一次开张了。

这所谓的大厅,就是我们的内存。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值