【synchronized底层原理之2】悲观锁与乐观锁、线程阻塞的代价等

悲观锁与乐观锁

悲观锁(Pessimistic Lock)

悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,因此每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。因为数据进行加锁,期间对该数据进行读写的其余线程都会进行等待。java

synchronized的重量级锁就是悲观锁。AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。数据结构

乐观锁(Optimistic Lock)

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采起在写时先读出当前版本号,而后加锁操做(比较跟上一次的版本号,若是同样则更新),若是失败则要重复读-比较-写的操做。因为数据没有进行加锁,期间该数据能够被其余线程进行读写操做。并发

java中的乐观锁基本都是经过CAS操做实现的,CAS是一种更新的原子操做,比较当前值跟传入值是否同样,同样则更新,不然失败。框架

synchronized的偏向锁和轻量级锁就是乐观锁spa

★小结

在Java技术发展史上,最开始使用的是悲观锁,实践中发现使用悲观锁有不少缺点,因此又引入了乐观锁。操作系统

两种锁各有优缺点,读取频繁使用乐观锁(可是可能出现“脏”读),写入频繁使用悲观锁(上下文切换开销大,可是保证没有“脏”数据)。线程

synchronized的重量级锁就是悲观锁指针

synchronized的偏向锁和轻量级锁就是乐观锁对象

java线程阻塞的代价--主要是上下文切换

Java线程的上下文切换须要操做系统的介入

java的线程是映射到操做系统原生线程之上的,若是要阻塞或唤醒一个线程就须要操做系统介入。blog

操做系统切换线程须要在用户态与内核态之间切换:由于用户态和内核态内存空间各自独立,故而切换要传递变量和参数,这样的话系统消耗较大,费时

须要在用户态与内核态之间切换,这种切换会消耗大量的系统资源,由于用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态须要传递给许多变量、参数给内核,内核也须要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工做。

频繁的上下文切换很费时,若是同步代码执行所需时间比上下文切换时间都要短,那引入重量级锁切换上下文这种同步策略是失败的:因此synchronized从JKD1.6进行了改进,引入了偏向锁、轻量级锁

java对象头中的markword

markword是java对象数据结构中的一部分,对象的markword和java各类类型的锁密切相关。

markword数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,它的最后2bit是锁状态标志位,用来标记当前对象的状态,对象的所处的状态,决定了markword存储的内容,以下表所示

32位虚拟机在不一样状态下markword结构以下图所示

了解了markword结构,有助于了解java锁的加锁解锁过程。

相关文章
相关标签/搜索