乐观锁与悲观锁对比

在这里插入图片描述

乐观锁

CAS(compare and swap):先比较再替换是乐观锁的一种实现思想,乐观锁是一种轻量级的锁。

循环等待问题:如果CAS操作长时间不成功的话,就会一直自旋,占用CPU。

ABA问题:
线程1读取了数据A
线程2读取了数据A
线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B
线程3读取了数据B
线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A
线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值。

ABA问题解决方法:
加标志位,例如搞个自增的字段,操作一次就自增加一,或者搞个时间戳,比较时间戳的值。

只能保证单个元素的原子性
CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了,JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。

一般数据库查询,订单表等高并发的地方使用乐观锁比较多。

悲观锁

synchronized加锁,synchronized 是最常用的线程同步手段之一,上面提到的CAS是乐观锁的实现,synchronized就是悲观锁了。

synchronized 对对象进行加锁,在 JVM 中,对象在内存中分为三块区域:对象头(Header)、实例数据(Instance
Data)和对齐填充(Padding)。

对象头:我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
在这里插入图片描述
在这里插入图片描述
当 Monitor 被某个线程持有后,就会处于锁定状态,如图中的 Owner 部分,会指向持有 Monitor 对象的线程。

另外 Monitor 中还有两个队列分别是EntryList和WaitList,主要是用来存放进入及等待获取锁的线程。

如果线程进入,则得到当前对象锁,那么别的线程在该类所有对象上的任何操作都不能进行。

synchronized应用在对象上:

每个对象都会与一个monitor相关联,当某个monitor被拥有之后就会被锁住,当线程执行到monitorenter指令时,就会去尝试获得对应的monitor。

步骤如下:

每个monitor维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为 1 。
当同一个线程再次获得该monitor的时候,计数器再次自增;
当不同线程想要获得该monitor的时候,就会被阻塞。
当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。

当计数器为0的时候,monitor将被释放,其他线程便可以获得monitor。

synchronized应用在方法上

同步方式是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现

synchronized应用代码块上

同步代码块是通过monitorenter和monitorexit来实现。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章