1.锁的分类
-
自旋锁: 线程状态及上下文切换消耗系统资源,当访问共享资源的时间短,频繁上下文切换不值得。jvm实
现,使线程在没获得锁的时候,不被挂起,转而执行空循环,循环几次之后,如果还没能获得锁,则被挂起 阻塞锁:阻塞锁改变了线程的运行状态,让线程进入阻塞状态进行等待,当获得相应的信号(唤醒或者时间) 时,才可以进入线程的准备就绪状态,转为就绪状态的所有线程,通过竞争,进入运行状态
-
重入锁:支持线程再次进入的锁,就跟我们有房间钥匙,可以多次进入房间类似
-
读写锁: 两把锁,读锁跟写锁,写写互斥、读写互斥、读读共享
-
互斥锁: 上厕所,进门之后就把门关了,不让其他人进来
-
悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上
锁,这样别人想拿这个数据就会阻塞直到它拿到锁
-
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别
人有没有去更新这个数据,可以使用版本号等机制。
-
公平锁:大家都老老实实排队,对大家而言都很公平
-
非公平锁:一部分人排着队,但是新来的可能插队
-
偏向锁:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的
线程才会释放锁
-
独占锁:独占锁模式下,每次只能有一个线程能持有锁
-
共享锁:允许多个线程同时获取锁,并发访问共享资源
2.深入理解Lock接口
Lock的使用 lock与synchronized的区别 lock 获取锁与释放锁的过程,都需要程序员手动的控制 Lock用的是乐观锁方式。
所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作
synchronized托管给jvm执行 原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味 着其他线程只能依靠阻塞来等待线程释放锁。 实现了lock接口的锁 各个方法的简介
1 | |
3.实现属于自己的锁
实现lock接口 使用wait notify
4.AbstractQueuedSynchronizer浅析
AbstractQueuedSynchronizer – 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。
此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。 子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。 假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。
子类可以维护其他状态字段,但只是为了获得同步而只追踪使用getState()、setState(int) 和compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。
应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。
而是定义了诸如 acquireInterruptibly(int) 之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。
此类支持默认的独占 模式和共享 模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。 处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock 中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。 此类通过支持独占模式的子类定义了一个嵌套的 AbstractQueuedSynchronizer.ConditionObject 类,可以将这个类用作 Condition 实现。isHeldExclusively() 方法将报告同步对于当前线程是否是独占的;使用当前 getState() 值调用release(int) 方法则可以完全释放此对象;如果给定保存的状态值,那么 acquire(int) 方法可以将此对象最终恢复为它以前获取的状态。
没有别的 AbstractQueuedSynchronizer 方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject 的行为当然取决于其同步器实现的语义。此类为内部队列提供了检查、检测和监视方法,还为 condition 对象提供了类似方法。可以根据需要使用用于其同步 机制的 AbstractQueuedSynchronizer 将这些方法导出到类中。 此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个 readObject 方法,该方法在反序列化时将此对象恢复到某个已知初始状态。 tryAcquire(int) tryRelease(int) tryAcquireShared(int) tryReleaseShared(int) isHeldExclusively() Acquire: while (!tryAcquire(arg)) { enqueue thread if it is not already queued; possibly block current thread; } Release: if ((arg)) unblock the first queued thread;
5.深入剖析ReentrantLock源码之非公平锁的实现
如何阅读源码? 一段简单的代码 看构造 看类之间的关系,形成关系图 看使用到的方法,并逐步理解,边看 代码边看注释 debug
6.深入剖析ReentrantLock源码之公平锁的实现
6.1公平锁与非公平锁的区别
公平锁:顾名思义–公平,大家老老实实排队
非公平锁:只要有机会,就先尝试抢占资源 公平锁与非公平锁 其实有点像在公厕上厕所。公平锁遵守排队的规则,只要前面有人在排队,那么刚进来的就老老实实排队。而 非公平锁就有点流氓,只要当前茅坑没人,它就占了那个茅坑,不管后面的人排了多久。
6.2源码解析
1 | |
非公平锁的弊端 可能导致后面排队等待的线程等不到相应的cpu资源,从而引起线程饥饿
7.掌控线程执行顺序之多线程debug
8.读写锁特性及ReentrantReadWriteLock的使用
特性:写写互斥、读写互斥、读读共享 锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁 降级的特性。
9.源码探秘之AQS如何用单一int值表示读写两种状态
10.深入剖析ReentrantReadWriteLock之读锁源码实现
int 是32位,将其拆分成两个无符号short 高位表示读锁 低位表示写锁 0000000000000000 0000000000000000 两种锁的最大次数均为65535也即是2的16次方减去1 读锁: 每次都从当前的状态加上65536 0000000000000000 0000000000000000
0000000000000001 0000000000000000
0000000000000001 0000000000000000
0000000000000001 0000000000000000
0000000000000010 0000000000000000 获取读锁个数,将state整个无符号右移16位就可得出读锁的个数 0000000000000001 写锁:每次都直接加1 0000000000000000 0000000000000000
0000000000000000 0000000000000001
0000000000000000 0000000000000001 获取写锁的个数 0000000000000000 0000000000000001
0000000000000000 1111111111111111
0000000000000000 0000000000000001
11. 深入剖析ReentrantReadWriteLock之写锁源码实现
0000000000000000 0000000000000001
0000000000000000 111111111111111111 0000000000000000 0000000000000001
12.锁降级详解
锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁 降级的特性。 注意点:锁降级之后,写锁并不会直接降级成读锁,不会随着读锁的释放而释放,因此需要显式地释放写锁
12.1是否有锁升级?
在ReentrantReadWriteLock里面,不存在锁升级这一说法 锁降级的应用场景 用于对数据比较敏感,需要在对数据修改之后,获取到修改后的值,并进行接下来的其他操作
13.StampedLock原理及使用
1.8之前,锁已经那么多了,为什么还要有StampedLock? 一般应用,都是读多写少,ReentrantReadWriteLock 因读写互斥,故读时阻塞写,因而性能上上不去。可能 会使写线程饥饿
13.1 StampedLock的特点
所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功; 所有释放锁的 方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致; StampedLock是 不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁) 支持锁升级跟锁降级 可 以乐观读也可以悲观读 使用有限次自旋,增加锁获得的几率,避免上下文切换带来的开销 乐观读不阻塞写 操作,悲观读,阻塞写得操作
13.2 StampedLock的优点
相比于ReentrantReadWriteLock,吞吐量大幅提升
13.13 StampedLock的缺点
api相对复杂,容易用错 内部实现相比于ReentrantReadWriteLock复杂得多 StampedLock的原理 每次获取锁的时候,都会返回一个邮戳(stamp),相当于mysql里的version字段 释放锁的时候,再根据之 前的获得的邮戳,去进行锁释放 使用stampedLock注意点 如果使用乐观读,一定要判断返回的邮戳是否是一开始获得到的,如果不是,要去获取悲观读锁,再次去读取
1 | |