本文共 2901 字,大约阅读时间需要 9 分钟。
当前对象类型 | 对象头长度 |
---|---|
数组 | 3字长 |
非数组 | 2字长 |
长度 | 内容 | 说明 |
---|---|---|
1个字长(32bit/64bit) | Mark World | 存储对象的hashCode以及锁的信息 |
1个字长 | Class Metedata Address | 存储对象类型数据的指针 |
1个字长 | Array length | 数组长度(如果对象是数组) |
锁状态 | 25bit | 4bit | 1bit偏向锁标志位 | 2bit锁标志位 |
---|---|---|---|---|
无锁 | hashCode | 对象分代年龄 | 0 | 01 |
锁状态 | 30bit | 2bit锁标志位 |
---|---|---|
轻量级锁 | 指向线程栈中锁记录的指针 | 00 |
锁状态 | 30bit | 2bit锁标志位 |
---|---|---|
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 |
锁状态 | 30bit | 2bit锁标志位 |
---|---|---|
GC标记 | 空 | 11 |
锁状态 | 23bit | 2bit | 1bit偏向锁标志位 | 2bit锁标志位 |
---|---|---|---|---|
偏向锁 | 线程ID | Epoch | 1 | 01 |
当获得锁的进程退出同步代码块时,用CAS操作比较:
发现对象头的MarkWord被改变(有锁竞争时,会将锁标志位标记为重量级锁),则释放锁并从阻塞队列中唤醒等待的线程。
发现对象头中的MarkWord没有改变,则恢复MarkWord为初始状态(其他线程可以获取了)。
经过轻量级锁变化得到的重量级锁。转化为重量级锁的前提是:多个线程存在竞争锁。
锁类型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁解锁不需要额外消耗,和执行非同步方法差不多 | 如果存在锁竞争,会带来额外锁撤销的消耗 | 只有一个线程访问同步块 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 得不到锁竞争的线程,自旋消耗CPU | 追求响应时间,同步块执行速度非常快 |
重量级锁 | 线程不使用自旋,不消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 |
原子性
CAS是底层提供的一个调用。
CAS(expected_a,altered_a)
current_a:函数调用时,从内存中读取到的a的值
expected_a:调用这个函数时,你认为当前的a的值
altered_a:你希望修改后的a的值
如果内存中的a值和你认为的a值一致,即在当前线程对a变量进行修改时,没有其他线程对a进行过修改,当前修改操作成功,返回成功。IF current_a == expected_a: current_a = altered_a RETURN TRUE如果值不一致,即当前线程对a变量进行修改时,有其他线程对a变量进行修改过,当前修改操作终止,返回失败。ELSE RETURN FALSE
两个线程进行同步操作,线程1获取到共享变量V的值为A,然后此时线程2对共享变量V进行操作将它改为B,然后又改为了A,此时线程1进行CAS操作发现V的值还是A,认为这段时间没有线程对V进行操作,然后执行对V的操作。这听上去没有什么问题,然而实际应用中会带来意想不到的问题。
这个问题存在的根本原因是CAS只对值进行判断,会丢失某些线程对变量的操作。
以一个简单的促销活动为例,某购物网站举行促销活动,活动规则如下:
只要当前账户余额小于100元的,免费发50元。
假设线程1就是这个发放代金券的线程,线程1取得当前顾客的账户余额值A,
线程2也是一个发放代金券的线程,线程2在此时也取得了这个顾客的账户余额A,发现A<100,然后线程2就执行CAS操作,给当前账户进行加50元操作,此时账户余额为B,B=A+50。
与此同时,顾客在门店进行消费了50元,此时账户余额变为A,
然后线程1进行判断,发现A<100,所以进行CAS操作,比对了一下余额没有发现变化,它认为这段时间没有人带这个余额变量进行操作过,所以执行账户余额就变为A+50。
最后导致网站对顾客多执行了一次发放补助操作。试想如果对每个顾客的余额进行操作都遇到这种情景,那还不亏到哭。
加版本号,对每个变量不仅仅记录他的值,而且记录发生时的版本(比如通过添加一个flag,来标记是否被处理过,或者记录时间等信息),将这两个值作为一个整体,让CAS进行判断。
这样每次比对时不只是比对值,还比对版本号,就不会发生ABA问题了。
类似的处理还有数据库中对数据行的操作也可以采用加版本号来解决。
如果循环CAS操作一直不成功,会给CPU带来很大开销。
将多个共享变量合并成一个共享变量来操作,
读写锁中,将读状态和写状态放在一个变量中,低16位表示写状态,高16位表示读状态。
转载地址:http://jebti.baihongyu.com/