说到线程安全,经常会用到锁。了解锁有哪些,锁又是如何设计的,对在业务中选择合适的锁很重要。
(一)锁的分类概述
(二)锁的分类详解
1.可重入锁 、不可重入锁
类型 | 概念(同一个线程角度分析)类型 | 模型 |
---|---|---|
可重入锁 | 可以重入上锁的代码段 | 方法调用时,次都可访问另一个方法(例如:递归) |
不可重入锁 | 不可以重入上锁后的代码段 | 独立的访问每一个方法,加锁 - 释放 |
2.共享锁、排他锁
类型 | 概念 | 示例 |
---|---|---|
共享锁 | 可被多个线程所持有 | ReadWriteLock的读锁 |
排他锁 | 一次只能被一个线程所持有 | ReadWriteLock的写锁 |
3.公平锁、非公平锁
类型 | 概念(多个线程角度分析) | 特点 |
---|---|---|
公平锁 | 按照申请锁的顺序来获取锁 | 公平 |
非公平锁 | 获取锁的顺序并不是按照申请锁的顺序 | 吞吐量比公平锁大,但可能造成饥饿现象 |
4.偏向锁、轻量级锁、重量级锁(锁的状态)
- Java 5通过引入锁升级的机制来实现高效Synchronized,通过对象监视器在对象头中的字段来表明。
类型 | 概念 | 特点 |
---|---|---|
偏向锁 | 一段同步代码一直被一个线程所访问,那么该线程会自动获取锁 | 降低获取锁的代价 |
轻量级锁 | 当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁 | 不会阻塞,提高性能 |
重量级锁 | 锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁 | 重量级锁会让他申请的线程进入阻塞,性能降低 |
5.乐观锁、悲观锁(看待并发同步的角度)
类型 | 概念 | 场景 |
---|---|---|
乐观锁 | 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。 | 多读的,提高吞吐量 |
悲观锁 | 对于同一个数据的并发操作,一定会发生修改的(实际上可能未修改)。因此对于同一份数据的并发操作,采取加锁的形式。 | 加锁失败,说明该记录正在被修改,当前查询可能要等待或者抛出异常(开发者决定) |
6.自旋锁
类型 | 概念 | 特点 |
---|---|---|
自旋锁 | 概念尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁 | 减少线程上下文切换的消耗;但是循环会消耗CPU |
(三)锁的设计
锁的设计用锁的粒度很好解释,锁的粒度涉及到临界区。临界区大,我们保护的范围更大,但是会影响效率,而临界区小了,可能又会导致不安全。
如何不知道如何选择,可以先保证安全,再进行优化。
临界区:我们居住的房子,门上都会有锁,而我们锁住的也是这个屋子的安全,而这个屋子就是临界区。
总结
了解了锁的分类,算是对锁有个基本认识了。要想更深入的了解锁,还需要了解锁底层原理,而常用锁底层实现都是 AQS实现的,下一篇我们就来聊聊【Java锁系列2/3–分析AQS源码】