要想理解 synchronized 原理,必须要了解 cas 和 用户态、内核态的理论
synchronized 是关键字,具体怎么实现要翻 cpp、汇编代码,记住理论就行了
CAS
全称叫 Compare And Swap 或者 Compare And Set,比较并交换、比较并设置。具体是:在执行操作之前,先比较当前内存中的值是否等于期望值,如果相等,则执行修改操作;如果不相等,则不执行修改操作,继续进行比较,直到内存中的值与期望值相等为止
举个例子,比如变量 a = 1,要做 a++ 操作
- 线程读取 a 的值,保存在自己的栈内(每个线程有自己的栈空间,变量的值保存在 cpu 寄存器)
- 现在已经拿到 a 的值了,做 a++,结果是 2
- 把 2 回写到 变量 a 中,不是直接回写,先再次读取a的值,判断是否符合条件才决定是否回写
- 再次读取 a 的值还是1(说明没有别的线程修改),就把2回写
- 如果再次读取的值不是1了,说明别的a已经被别的线程修改了,2就不会回写
- 如果重新读取的a不是1,重新做a++,重做的顺序和上面一样,直到成功+1
这个过程是有问题的,比较经典的是 ABA 问题,这个可以通过版本号解决
除了 ABA 还有个更深入的问题,a 一开始是1,a++ 做完,对比 a,然后把新的值写入 a 的过程中,对比和写入是必须保证原子性的,这条命令不能被打断,这是怎么保证的?java 调用 c++,c++ 调用汇编,给这条指令上了个锁(要证明就要去翻 jvm 的 cpp 代码,还要能看懂汇编代码)
用户态、内核态
这是操作系统的两种运行模式,不同模式有不同命令,内核态的命令一般程序不能直接调用执行,需要经过操作系统,操作系统执行这些高级别的命令。JVM 是已用户态的模式在运行,想要加一把重量级锁,这个只能用户态跟操作系统发起申请,操作系统接收到指令然后调用内核态的指令,这个开销是比较大的
比如 java 不能直接操作内存,只能间接通过 UnSafe 类访问,比如前端 js 不能操作系统文件,再比如普通程序不能直接修改操作系统内核结构等等
synchronized
加锁的方式就不说了,同步方法,同步代码块等
synchronized 可以给类加锁,可以给对象加锁,究竟是怎么加的?修改对象头信息,修改成功就表示加锁成功,可以使用 JOL 分析对象内存分布
jvm 启动 4 秒后开启偏向锁,可以在同步代码块中对一个对象加锁(比如 obj),程序启动在主方法中使用 JOL 分析对象内存分布,休眠5秒,然后调用同步代码块的方法,再使用 JOL 打印内存分布,会发现两次答应的对象头不一样
也可以配置延迟时间为0,jvm 一启动就开启偏向锁,锁升级过程如下
- 创建对象 obj,这时是无锁,对象头是 obj 的信息,没有和锁相关的信息
- 遇到 synchronized 时,加锁,这时加的是偏向锁
- 具体是对象头里 markword 的线程id改为当前持有锁的线程 id
- 对象头有哪个线程id,就表示这个对象被哪个线程持有锁
- 后续如果有重入或竞争先对比线程 id,如果 id 一致,没有竞争,一直是偏向锁
- 如果有线程竞争,升级为自旋锁
- 也叫轻量级锁、无锁(这个无锁指的是没加重量级锁),CAS 实现,别的线程一直自旋等待
- 具体是别的线程一直CAS自旋尝试将对象头的 markword 的线程 id 设置成自己的,如果设置成功就表示持有锁了
- 如果竞争加剧,升级为重量级锁
- 哪种条件视为竞争加剧?
- jdk1.6及以前:自旋次数超过10次,或自选线程数量超过cpu核数一半,自选次数可配置,--XX:PreBlockSpin
- jdk1.6 之后:由 JVM 自己判断自旋次数、自旋线程数是多少时视为竞争加剧
- 和内核态通信,向操作系统申请资源
- 哪种条件视为竞争加剧?
为什么有轻量级锁还要重量级锁?线程自旋是要消耗 CPU 的,当线程数量过多显然不适合继续自旋。重量级锁会把这些等待的线程放进一个队列,这个队列里面的线程不会消耗 CPU
标签:加锁,操作系统,synchronized,线程,自旋,id From: https://www.cnblogs.com/cyrushuang/p/18336488