Java基础:锁

概览

锁图

读写锁

读写锁

final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 读锁
lock.readLock().lock();
// 释放锁
lock.readLock().unlock();

乐观锁

乐观锁

// 如Java中的原子类,如 AtomicInteger里面的 compareAndSet
public final boolean compareAndSet(int expect, int update) {
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

悲观锁

悲观锁

自旋锁

自旋锁

可重入锁

可重入锁(递归锁)

重量级锁

重量级锁

轻量级锁

轻量级锁

共享锁

共享锁

独占锁

独占锁

公平锁

公平锁

非公平锁

非公平锁

备注:图片来自BI数据分析


锁升级过程

Java对象的锁信息存储在对象头中的Mark Word字段中。Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下图:

32位JVM的Mark Word的默认存储结构

我们需要明确一点:偏向锁、轻量级锁、重量级锁只针对synchronized。

Java中锁的状态有四种:

1.无锁状态

2.偏向锁

顾名思义,偏向某一个线程,当线程数目不多的时候,由于反复获取锁会使得我们的运行效率下降,于是出现了偏向级锁。JVM使用CAS操作把线程ID记录到对象的Mark Word当中,并修改标识位,那么当前线程就拥有了这把锁。

偏向锁

偏向级锁不需要操作系统的介入,JVM使用CAS操作将线程ID放入对象的Mark Word字段中,于是线程获得了锁,可以执行synchronized代码块的内容,当线程再次执行到这个synchronized的时候,JVM通过锁对象的Mark Word判断 :当前线程ID还存在,还持有这个对象的锁,于是就可以继续进入临界区执行,而不需要再次获得锁

偏向锁,在没有别的线程竞争的时候,一直偏向当前线程,当前线程就可以一直进入synchronized修饰的代码块一直运行。

如果在运行中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁升级到轻量级锁

偏向级锁就是为了消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。

3.自旋锁(轻量级锁)

轻量级锁是由偏向级锁升级来的,当一个线程运行同步代码块时,另一个线程也加入想要运行这个同步代码块时,偏向锁就会升级为轻量级锁。

首先,JVM会将锁对象的Mark Word恢复成为无锁状态,在当前两线程的栈帧中各自分配一个空间,叫做Lock Record,把锁对象account的Mark Word在两线程的栈帧中各自复制了一份,官方称为:Displaced Mark Word

然后一个线程尝试使用CAS将对象头中的Mak Word替换为指向锁记录的指针,如果替换成功,则当前线程获得锁,如果失败,则当前线程自旋重新尝试获取锁。当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两个或两个以上线程竞争同一个锁),则轻量级锁会膨胀成重量级锁

举个例子: 线程A、线程B同时想要执行一个同步代码块,假设线程A抢到了锁,则线程A的Lock Record的地址会被CAS操作放到了锁对象Mark Word中,并且将锁标志位改为00,这意味着线程A就获取到了该锁,可以执行同步代码块。而线程B没有抢到锁,但是线程B不会阻塞,而是通过自旋的方式,等待获取锁。(一般默认自旋10次),如果线程A释放掉锁,则将线程A中的Displaced mark word使用CAS复制回锁对象的Mark Word字段,此时线程B就可以获取锁对象,如果线程B还没有获取成功,则说明同时存在两个或两个以上的线程同时竞争这一把锁,轻量级锁会升级成为重量级锁。

轻量级锁

4.重量级锁

多个线程竞争同一个锁时,会导致除锁的拥有者外,其余线程都会自旋,这将导致自旋次数过多,cpu效率下降,所以会将锁升级为重量级锁。

重量级锁需要操作系统的介入,依赖操作系统底层的Muptex LockJVM会创建一个monitor对象,把这个对象的地址更新到Mark Word中

当一个线程获取了该锁后,其余线程想要获取锁,必须等到这个线程释放锁后才可能获取到,没有获取到锁的线程,就进入了阻塞状态。

重量级锁

Java中默认每个对象都关联一个同步监视器(monitor),即每个对象都可作为一个锁。不加锁的时候,就是无锁状态 ,加锁之后有一个线程访问时则升级为偏向锁,就是偏向第一个访问的线程。随着访问的线程变多,就升级为自旋锁。说下自旋锁,在Java最初的版本中,自旋锁中有两个重要的参数,一个就是自旋次数,默认是10,当一个线程在等锁时会不停的自旋(底层就是一个while循环),当自旋的线程达到CPU核数的1/2时,就会升级为重量级锁----synchronized。但在jdk1.6之后改为自适应自旋,自旋次数是根据程序历史数据等等判断自旋的数量。

自旋就像你去上厕所,发现里面有人,然后你就提着裤子转圈,当上厕所的人多了,一堆人在外面提裤子转,当人数变得多时(肯定有一个上限),就得大管家(操作系统)出面,掏出它那令人畏惧的大锁Synchronized,来管理这些上厕所的人(线程)了。

为什么会有两种锁?既然有轻重之分,其实是在效率上要考虑的问题,你想啊,jvm能自己即时处理的问题,再麻烦操作系统,找操作系统申请重量级锁是花费时间的。

执行时间长的,线程多的情况下---->重量级锁(悲观锁)

执行时间短,线程少的情况下---->自旋锁(乐观锁)

注意:锁只能升级而不能降级,即一个锁从偏向级锁升级到轻量锁时,不能再重新回到偏向级锁。


展开阅读全文

页面更新:2024-03-13

标签:字段   线程   悲观   公平   操作系统   对象   状态   两个   竞争   代码   基础

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top