各种锁你必须搞明白哈

简介: 《基础》系列

synchronized底层实现原理

Java 对象底层都会关联一个 monitor,使用 synchronized 时 JVM 会根据使用环境找到对象的 monitor,根据 monitor 的状态进行加解锁的判断。如果成功加锁就成为该 monitor 的唯一持有者,monitor 在被释放前不能再被其他线程获取。

synchronized在JVM编译后会产生monitorenter 和 monitorexit 这两个字节码指令,获取和释放 monitor。这两个字节码指令都需要一个引用类型的参数指明要锁定和解锁的对象,对于同步普通方法,锁是当前实例对象;对于静态同步方法,锁是当前类的 Class 对象;对于同步方法块,锁是 synchronized 括号里的对象。

执行 monitorenter 指令时,首先尝试获取对象锁。如果这个对象没有被锁定,或当前线程已经持有锁,就把锁的计数器加 1,执行 monitorexit 指令时会将锁计数器减 1。一旦计数器为 0 锁随即就被释放。

synchronized关键词使用方法

  • 直接修饰某个实例方法
  • 直接修饰某个静态方法
  • 修饰代码块

简述Java偏向锁

JDK 1.6 中提出了偏向锁的概念。该锁提出的原因是,开发者发现多数情况下锁并不存在竞争,一把锁往往是由同一个线程获得的。偏向锁并不会主动释放,这样每次偏向锁进入的时候都会判断该资源是否是偏向自己的,如果是偏向自己的则不需要进行额外的操作,直接可以进入同步操作。

其申请流程为:

  • 首先需要判断对象的 Mark Word 是否属于偏向模式,如果不属于,那就进入轻量级锁判断逻辑。否则继续下一步判断;
  • 判断目前请求锁的线程 ID 是否和偏向锁本身记录的线程 ID 一致。如果一致,继续下一步的判断,如果不一致,跳转到步骤4;
  • 判断是否需要重偏向。如果不用的话,直接获得偏向锁;
  • 利用 CAS 算法将对象的 Mark Word 进行更改,使线程 ID 部分换成本线程 ID。如果更换成功,则重偏向完成,获得偏向锁。如果失败,则说明有多线程竞争,升级为轻量级锁。

简述轻量级锁

轻量级锁是为了在没有竞争的前提下减少重量级锁出现并导致的性能消耗。

其申请流程为:

  • 如果同步对象没有被锁定,虚拟机将在当前线程的栈帧中建立一个锁记录空间,存储锁对象目前 Mark Word 的拷贝。
  • 虚拟机使用 CAS 尝试把对象的 Mark Word 更新为指向锁记录的指针
  • 如果更新成功即代表该线程拥有了锁,锁标志位将转变为 00,表示处于轻量级锁定状态。
  • 如果更新失败就意味着至少存在一条线程与当前线程竞争。虚拟机检查对象的 Mark Word 是否指向当前线程的栈帧
  • 如果指向当前线程的栈帧,说明当前线程已经拥有了锁,直接进入同步块继续执行
  • 如果不是则说明锁对象已经被其他线程抢占。
  • 如果出现两条以上线程争用同一个锁,轻量级锁就不再有效,将膨胀为重量级锁,锁标志状态变为 10,此时Mark Word 存储的就是指向重量级锁的指针,后面等待锁的线程也必须阻塞。

简述锁优化策略

即自适应自旋、锁消除、锁粗化、锁升级等策略偏。

简述Java的自旋锁

线程获取锁失败后,可以采用这样的策略,可以不放弃 CPU ,不停的重试内重试,这种操作也称为自旋锁。

简述自适应自旋锁

自适应自旋锁自旋次数不再人为设定,通常由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。

简述锁粗化

锁粗化的思想就是扩大加锁范围,避免反复的加锁和解锁。

简述锁消除

锁消除是一种更为彻底的优化,在编译时,Java编译器对运行上下文进行扫描,去除不可能存在共享资源竞争的锁。

简述Lock与ReentrantLock

Lock接口是 Java并发包的顶层接口。

可重入锁 ReentrantLock 是 Lock 最常见的实现,与 synchronized 一样可重入。ReentrantLock 在默认情况下是非公平的,可以通过构造方法指定公平锁。一旦使用了公平锁,性能会下降。

简述AQS

AQS(AbstractQuenedSynchronizer)抽象的队列式同步器。AQS是将每一条请求共享资源的线程封装成一个锁队列的一个结点(Node),来实现锁的分配。AQS是用来构建锁或其他同步组件的基础框架,它使用一个 volatile int state 变量作为共享资源,如果线程获取资源失败,则进入同步队列等待;如果获取成功就执行临界区代码,释放资源时会通知同步队列中的等待线程。

子类通过继承同步器并实现它的抽象方法getState、setState 和 compareAndSetState对同步状态进行更改。

AQS获取独占锁/释放独占锁原理:

获取:(acquire)

  • 调用 tryAcquire 方法安全地获取线程同步状态,获取失败的线程会被构造同步节点并通过 addWaiter 方法加入到同步队列的尾部,在队列中自旋。
  • 调用 acquireQueued 方法使得该节点以死循环的方式获取同步状态,如果获取不到则阻塞。

释放:(release)

  • 调用 tryRelease 方法释放同步状态
  • 调用 unparkSuccessor 方法唤醒头节点的后继节点,使后继节点重新尝试获取同步状态。

AQS获取共享锁/释放共享锁原理

获取锁(acquireShared)

  • 调用 tryAcquireShared 方法尝试获取同步状态,返回值不小于 0 表示能获取同步状态。
  • 释放(releaseShared),并唤醒后续处于等待状态的节点。
相关文章
|
10小时前
|
druid 数据库
一篇文章讲明白HearthBuddy卡组
一篇文章讲明白HearthBuddy卡组
|
1月前
|
Java
面试官:说一说CyclicBarrier的妙用!我:这个没用过...
【5月更文挑战第5天】面试官:说一说CyclicBarrier的妙用!我:这个没用过...
21 2
|
1月前
|
消息中间件 安全 算法
通透!从头到脚讲明白线程锁
线程锁在分布式应用中是重中之重,当谈论线程锁时,通常指的是在多线程编程中使用的同步机制,它可以确保在同一时刻只有一个线程能够访问共享资源,从而避免竞争条件和数据不一致性问题。
|
9月前
|
Python
我这样回答多线程并发,面试官非要跟我做朋友!
我这样回答多线程并发,面试官非要跟我做朋友!
101 0
|
11月前
|
安全
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
71 0
什么是死锁?(把死锁给大家讲明白,知道是什么,为什么用,怎么用)
|
Java 调度
不懂什么是锁?看看这篇你就明白了(五)
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
85 0
不懂什么是锁?看看这篇你就明白了(五)
|
安全 Java
不懂什么是锁?看看这篇你就明白了(四)
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
76 0
不懂什么是锁?看看这篇你就明白了(四)
|
SQL 算法 安全
不懂什么是锁?看看这篇你就明白了(一)
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
124 0
不懂什么是锁?看看这篇你就明白了(一)
|
缓存 算法 Java
不懂什么是锁?看看这篇你就明白了(二)
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
85 0
不懂什么是锁?看看这篇你就明白了(二)
|
存储 安全 Java
不懂什么是锁?看看这篇你就明白了(三)
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
75 0
不懂什么是锁?看看这篇你就明白了(三)