多线程锁的概要
首先对于锁的条件和要点进行一个总结:
- 锁使用来保护代码片段的, 以保证多线程的安全性, 一次只允许一个线程执行被保护的代码.
- 锁可以管理视图进入被保护代码的线程, 提高多线程安全
- 一个锁可以有一个或多个关联的条件对象
对于synchronized来说, 如果它修饰的是方法, 那么他保护的代码段将是整个方法
Synchronized关键字
结合锁策略, 可以得出以下结论:
- synchronized开始是乐观锁, 如果冲突太频繁了, 那么synchronized就升级为悲观锁
- synchronized开始是轻量级锁, 如果单个线程占用synchronized锁的时间长了, 就会变成重量级锁.
- 此处的轻量级锁和自旋锁有重叠的部分
- synchronized是一种不公平的锁
- synchronized是一种可重入锁
- synchronized不是读写锁
synchronized加锁过程
- 首先对一个无锁的代码段进行加锁, 这个锁会进入偏向锁的状态, 此处的偏向锁不是真正的加锁, 而是给这个锁做一个标记, 表示这个锁属于哪个线程, 如果后续没有线程来抢锁, 那么就不会进行其他同步操作. 如果有的话, 那么这个锁就会升级为真正的锁, 进入一种以自旋锁为基础的轻量级锁状态
- 进入轻量级锁, 此处的轻量级锁是通过CAS实现的, 轻量级锁适用于抢锁比较少的多线程环境, 基于自旋锁, 如果其他线程来抢锁就会阻塞等待, 并且在极短时间内进行第二次访问, 直到拿到锁. 但是如果锁长时间没有释放就会造成CPU空转, 浪费CPU资源. 所以如果有多个线程频繁的抢锁, 锁就会升级为重量级锁. 让线程放弃CPU, 进入内核态,此时有操作系统来通知线程这个锁是否被释放, 然后再来调度这些抢锁的线程.
- 轻量级锁的竞争激烈后, 就会升级为重量级锁,如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.经过一段时间后, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒这个线程, 尝试重新获取锁.
synchronized锁优化
锁消除
遵循于非必要不加锁的规则, synchronized实现了锁消除机制, 也就是在非多线程情况下, synchronized会自动识别线程情况, 自动消除掉不必要的锁,
例如我们常用的线程安全的StringBuffer, 但是如果在单线程使用, 那么就会在编译的时候去除synchronized关键字, 来减少非必要的开销.
StringBuffer sb = new StringBuffer(); sb.append("a"); sb.append("b"); sb.append("c"); sb.append("d");
此处每次的append操作都会加锁, 造成了非必要的资源浪费
锁粗化
在一段代码中, 可能存在两个被保护的代码段, 如下:
如果这两个代码段(代码段1和代码段2)之间, 不存在锁竞争的情况话, 就可以省去中间的解锁和加锁, 这样就避免的多余的加锁解锁操作, 节约了CPU资源