乐观锁
悲观锁
自旋锁
在短时间内,持有锁的线程能够释放资源,等待的线程不必做内核态与用户态之间的切换,只需要做一个自旋的操作等待持有锁的线程释放锁就可以立即获得锁。
Synchronized同步锁
- 独占式悲观锁
- 可重入锁
- 非空对象可以加锁
作用范围
- 作用于方法时:锁住对象实例
- 作用于静态方法时,锁住Class实例,相当于类的全局锁,所有调用该方法的线程都会被锁。
- 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。
核心组件
- wait Set:
- Contention List
- Entry List
- OnDeck
- Owner
实现
Synchronized是非公平锁,等待的线程会先自旋尝试获取锁。
ReentrantLock
- 可重入锁
- 响应中断锁
- 可轮询锁请求
- 定时锁
默认是非公平锁,可以初始化为公平锁。
ReentrantLock与synchronized
- ReentrantLock通过lock与unlock进行加锁解锁;synchronized会被JVM自动解锁机制。使用ReentrantLock必须在finally中进行解锁操作。
- ReentrantLock相比synchronized的优势是可以中断、公平锁、多个锁。
Condition类和Object类锁的区别
- Condition类的await和Object的wait方法等效。
- Condition类的signal和Object类的notify方法等效。
- Condition类的signAll和Object类的notifyAll方法等效。
- ReentrantLock类可以唤醒指定条件的线程,而object唤醒是随机的。
tryLock和lock和lockInterruptibly区别
- tryLock能获得锁就返回true,不能就立即返回false, tryLock(long timeout, TimeUnit unit),可以增加时间限制,如果超过时间还没获得锁,返回false;
- lock能获得锁就返回true,不能的话就一直等待获得锁。
- lock和lockInterruptily,如果两个线程分别执行这两个方法,但此时中断这俩线程,lock不会抛出异常,而lockInterruptily会抛出异常。
信号量(Semaphore)
是一种基于计数的信号量。超过设定的阈值后线程申请会被阻塞。
与ReentrantLock比较
- 基本能够完成Reentrant的所有工作,通过accquire与release方法获得和释放临界资源。Semaphone.acquire()方法默认可以相应中断锁,与ReentrantLock.lockInterruptibly()作用效果一致。
- Semaphore也实现了可轮询锁请求与定时锁的功能。也提供了非公平锁与公平锁机制。
- 必须在finally中释放锁。
ReadWriteLock读写锁
分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥。
共享锁和独占锁
- java并发包提供的加锁模式分为独占锁和共享锁。
独占锁
每次只能有一个线程持有锁。ReentrantLock
共享锁
允许多个线程同时获取锁,并发访问共享资源。
锁的状态
无锁状态、偏向锁、轻量级锁、重量级锁
重量级锁
Synchronized是通过对象内部一个monitor实现的。监视器锁(monitor)本质是依赖操作系统底层实现的,依赖于Mutex Lock。所以做线程切换时,需要进行用户态与内核态之间的切换,这成本很高。
轻量级锁
随着锁竞争,锁可以从偏向级锁升级到轻量级锁,再升级到重量级锁,不会存在降级的情况。
轻量级锁不能代替重量级锁,本质上是在没有多线程竞争的前提下,减少传统重量级锁使用产生的性能上的消耗。
适应场景:
线程交替执行 同步块的情况,如果存在许多线程同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
偏向锁
偏向锁的目的是在某个线程获得锁之后,消除这个线程锁的重入的开销。
引入偏向锁是为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取和释放依赖于多次的CAS原子指令,而偏向锁只需要在置换线程的时候依赖一次CAS。
适应场景:
只有一个线程执行同步块时进一步提高性能。
分段锁
是一种思想,ConcurrentHashMap是学习分段锁的最好实践。
锁优化
减少锁持有时间
减小锁粒度(ConcurrentHashMap)
锁分离 (读写锁)
锁粗化
锁消除(编译时发现不可能共享的对象会消除这个锁)
未完待续 未央书斋 温故知新