概览 锁图读写锁 读写锁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 Lock 。JVM会创建一个monitor对象,把这个对象的地址更新到Mark Word中 。 当一个线程获取了该锁后,其余线程想要获取锁,必须等到这个线程释放锁后才可能获取到,没有获取到锁的线程,就进入了阻塞状态。 重量级锁 Java中默认每个对象都关联一个同步监视器(monitor) ,即每个对象都可作为一个锁。不加锁的时候,就是无锁状态 ,加锁之后有一个线程访问时则升级为偏向锁 ,就是偏向第一个访问的线程。随着访问的线程变多,就升级为自旋锁 。说下自旋锁,在Java最初的版本中,自旋锁中有两个重要的参数,一个就是自旋次数,默认是10 ,当一个线程在等锁时会不停的自旋(底层就是一个while循环),当自旋的线程达到CPU核数的1/2 时,就会升级为重量级锁----synchronized 。但在jdk1.6之后改为自适应自旋,自旋次数是根据程序历史数据等等判断自旋的数量。 自旋就像你去上厕所,发现里面有人,然后你就提着裤子转圈,当上厕所的人多了,一堆人在外面提裤子转,当人数变得多时(肯定有一个上限),就得大管家(操作系统)出面,掏出它那令人畏惧的大锁Synchronized,来管理这些上厕所的人(线程)了。 为什么会有两种锁?既然有轻重之分,其实是在效率上要考虑的问题,你想啊,jvm能自己即时处理的问题,再麻烦操作系统,找操作系统申请重量级锁是花费时间的。 执行时间长的,线程多的情况下---->重量级锁(悲观锁) 执行时间短,线程少的情况下---->自旋锁(乐观锁) 注意:锁只能升级而不能降级,即一个锁从偏向级锁升级到轻量锁时,不能再重新回到偏向级锁。