java理论知识汇总-锁篇

思维导图

关于Synchronize锁

Synchronize锁原理

synchronized 在代码块上是通过 monitorenter 和 monitorexit指令实现,在静态方法和方法上加锁是在方法的flags中加入 ACC_SYNCHRONIZED。

偏向锁概念

  • 就是这个锁会偏向于第一个获得它的线程,会在对象头存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。
  • 偏向锁只适用于只有一个线程反复进入同步代码块的场景,可以提高带有同步但无竞争的程序性能。

Synchronize偏向锁原理

  1. 线程第一次执行同步代码块时,先将对象头中的偏向锁的标记改为1。
  2. 使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,退出同步代码块。
  3. 这个线程第二次执行同步代码块时,只需要判断Mard Word中的线程ID和要获取锁的线程的ID是否相同,一样,就直接进来,效率很高。

Synchronize锁的撤销/升级

  1. 在安全点暂停拥有锁的线程,判断锁对象是否处于被锁定状态。
  2. 修改锁对象头标志位,撤销锁或者升级锁并唤醒线程。

Synchronize轻量级锁原理

线程并不立即堵塞,而是自旋尝试获取锁。适用于那些同步代码块执行的很快,竞争并不激烈的场景

Synchronize重量级锁

依赖操作系统的MutexLock(互斥锁)来实现的,未获取到锁的线程会立即堵塞,堵塞的线程虽然不会消耗CPU,但是唤醒和堵塞需要操作系统来帮忙,需要从用户态转系统态,这可能需要消耗比执行代码更多的时间。

Synchronize锁升级发生的时机

  • 第一次执行同步代码块时(无竞争),获取偏向锁
  • 两个线程来竞争锁时,升级为轻量级锁
  • 自旋超过指定次数依旧未获取到锁或者又来一个线程来竞争锁时,升级为重级锁。

关于Lock锁

公平性锁和非公平性锁

所谓公平锁,线程将按照他们发出请求的顺序来获取锁,不允许插队;但在非公平锁上,则允许插队:当一个线程发生获取锁的请求的时刻,如果这个锁是可用的,那这个线程将跳过所在队列里等待线程并获得锁。

ReentrantLock加锁过程

  1. cas尝试加锁
  2. 加锁失败加入包装成node加入队尾
  3. 自旋加锁/设置信号后暂停线程
  4. 异常发生或中断时的出队操作
  5. 线程中断处理

ReentrantLock的tryLock()和tryLock(long timeout, TimeUnit unit)

  • tryLock()无论是公平模式还是非公平模式,一律使用非公平方式获取锁
  • tryLock(long timeout, TimeUnit unit)会根据模式,使用对应的方式获取锁

ReentrantReadWriteLock如何维护状态

  • ReentrantReadWriteLock内部维护的读写状态是由32位码表示,高16位为读状态,表示持有读锁的线程数,低16位为写状态,表示写锁的重入次数 ,状态的改变通过AQS实现,保证同步

锁饥饿问题

由于非公平模式入队前都会尝试获取锁,在读多写少情况下,会有持续不断的共享读锁的获取被响应,于是写锁线程一直被堵塞

Condition

  • 必须先获取到锁。
  • await 加入条件队列,并堵塞当前线程。作用同Object.wait()
  • signal 唤醒条件队列的队首线程。作用同Object.notify()

为什么await、signal要先获取锁

  • 代码层面,await执行时会强制执行释放锁的操作,如果没有锁自然就会导致异常
  • await、signal操作的目的是基于某种条件, 协调多个线程间的运行状态,由于涉及到多个线程间基于共享变量的相互通信, 必然需要引入某种同步机制, 以确保wait(), notify() 操作在线程层面的原子性

StampedLock乐观读锁的使用

  • 尝试获得一个乐观读锁tryOptimisticRead()
  • 业务读逻辑
  • 验证获取乐观读锁后是否有写锁发生validate(stamp)
  • 有则自旋/转悲观锁,无则返回结果

StampedLock同ReentrantReadWriteLock的区别

  • 乐观读,StampedLock在读操作时不会阻塞写操作
  • 乐观读,StampedLock避免了写饥饿情况的发生
  • 不可重入,StampedLock写锁重复获取会导致死锁

StampedLock锁标示

  • state 二进制数前7位为读锁,后面都为写锁标记
  • 写锁的获取和释放不同于ReentrantReadWriteLock的+1、-1, StampedLock只会加1000 0000(128),该目的主要是为了乐观锁以及解决ABA问题

关于CAS和volatile

什么是CAS

意为比较并交换,至少需要三个参数,分别为内存地址A,旧的预期值E,需要修改的新值U。步骤为:

  1. 获取地址A上的值V。
  2. 比较 V 与 E 是否相等。(比较)
  3. 如果比较相等,将 U 写入 A。(交换)
  4. 返回操作是否成功。

CAS的原理

  • java 的 cas 利用的的是 unsafe 这个类提供的 cas 操作。
  • unsafe 的cas 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg
  • Atomic::cmpxchg 的实现使用了汇编的 cas 操作,并使用 cpu 硬件提供的 lock信号保证其原子性

CAS的ABA问题

ABA 的问题,就是一个值从A变成了B又变成了A,使用CAS操作不能发现这个值发生变化了,处理方式是可以使用携带类似时间戳的版本AtomicStampedReference

CPU如何实现原子操作

缓存锁定机制,也就说当某个处理器对缓存中的共享变量进行了操作,其他处理器会有个嗅探机制,将其他处理器的该共享变量的缓存失效,待其他线程读取时会重新从主内存中读取最新的数据。

AtomicStampedReference如何解决ABA问题

  • 携带版本号,即每次compareAndSet操作的时候都会根据版本号和对象引用生成Pair对象。
  • 将A->B->A 情况优化成了A1->B->A2,因此比较失败,交换就不会进行

volatile的原理

  • volatile修饰的共享变量进行写操作的时候会多出内存屏障指令
  • 内存屏障指令会引起处理器缓存写回内存;
  • 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  • 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

内存屏障

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的前面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障。
    解释
  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

举例说明volatile的原理

Thread-A写了变量i

  1. Thread-A发出LOCK#指令
  2. 发出的LOCK#指令锁总线(或锁缓存行),同时让Thread-B高速缓存中的缓存行内容失效
  3. Thread-A向主存回写最新修改的i

Thread-B读取变量i
1.Thread-B发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值

关于volatile的happens-before

volatile变量的写happens-before 于任意后续对volatile变量的读

volatile的指令重排序

  • 通过内存屏障实现的
  • volatile写是在前面和后面分别插入内存屏障(前写后读),而volatile读操作是在后面插入两个内存屏障

双重判断锁实现的单例模型为甚么要用volatile修饰

Instance instance = new Instance()粗略来说有3个步骤

  1. 分配内存空间
  2. 初始化对象
  3. instance指向分配的内存空间
    其中2和3可能发生重排序,导致获取到的单例对象未初始化。

volatile和synchronized比较

原子性 有序性 可见性
synchronized
volatile

关于AQS

AQS思维导图.png

AQS关键方法说明(搭配理解)

getState():返回同步状态的当前值;
setState(int newState):设置当前同步状态;
compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性;
tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
tryRelease(int arg):独占式释放同步状态;
tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
tryReleaseShared(int arg):共享式释放同步状态;
isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;
acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;
tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;
release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
releaseShared(int arg):共享式释放同步状态;

共享锁和独占锁获取锁时的区别

  • 共享锁可以被多个线程拥有,所以当一个节点获取到共享锁后会继续唤醒后继节点去获取锁。
  • 独占锁只能被一个线程拥有,所以当一个节点获取到独占锁后没有必要再唤醒它的后继节点了

关于LockSupport

LockSupport有什么用

  • park和unpark方法可以替代wait和notify的功能,而且不会造成死锁现象
  • blocker入参能够再dump线程的时候看到阻塞对象的信息

LockSupport同wait/notify的区别

  • wait和notify都是Object中的方法,在调用前必须先获得锁对象,LockSupport不用
  • notify无法唤醒指定的线程,LockSupport可以

LockSupport的park方法和unpark方法执行顺序颠倒为什么不会有问题

  • 主要依赖于UnSafe类的park和unpark底层的实现,它实际上维护着一个_counter的许可证。
  • unpark时将许可证设置为允许,并唤醒线程(如果有的话)
  • park时如果许可证为允许,那就不再堵塞线程。

关于Semaphore

Semaphore的原理分析

  • 底层通过AQS的功效锁实现,state即为令牌数
  • acquire获取令牌,获取不到时堵塞线程,并加入同步队列
  • release释放令牌,并依次唤醒同步队列中的堵塞线程去获取令牌,直到无令牌可用

关于CyclicBarrier

CyclicBarrier的原理分析

  • 底层通过ReentrantLock加锁。
  • 实例化时需要确定栅栏的数量及达标时的任务
  • 当调用await的线程数未达到指定数时,堵塞当前线程
  • 当调用await的线程数达到指定数时,就执行指定任务,后将堵塞线程唤醒并将CyclicBarrier对象复原以便再次利用

关于CountDownLatch

CountDownLatch的原理分析

  • 底层通过AQS的共享锁实现,state为计数器的值
  • 实例化时需要确定计数器的值
  • 调用await时,将当前线程堵塞,并加入同步队列
  • 调用countDown时,就将计数器的值减1,当计数器的值为0时,依次唤醒同步队列的线程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容