1. synchronized具体采用了哪些加锁策略?
- synchronized既是悲观锁, 也是乐观锁
- synchronized既是重量级锁, 也是轻量级锁
- synchronized重量锁部分是基于系统的互斥锁实现的; 轻量级锁部分是基于自旋锁实现的;
- synchronized是非公平锁 (不会遵守先来后到, 锁释放之后, 哪个线程拿到锁, 各凭本事)
- synchronized是可重入锁 (内部会记录哪个线程拿到了锁, 记录引用计数)
- synchronized不是读写锁
synchronized采取自适应的加锁策略!
2. synchronized内部实现策略(内部原理)
代码中写了一个 synchronized之后, 这里可能会产生一系列的"自适应的过程", 锁升级(锁膨胀);
2.1 偏向锁
- 不是真正的加锁, 而只是做了一个 “标记”, 如果有别的线程来竞争锁了, 才会真正的加锁; 如果没有别的线程竞争, 就自始至终不会真的加锁了
- 加锁本身, 有一定开销, 能不加, 就不加, 有人来竞争了, 才会真正的加锁~
2.2 轻量级锁与重量级锁
- synchronized通过自旋锁的方式来实现轻量级锁
- 我这边把锁给占了, 另一个线程就会按照自旋的方式, 来反复查询当前的状态是不是被释放了
- 但是, 后续, 如果竞争这边锁的线程越来越多了(锁冲突更激烈了), 从轻量级锁 升级 成重量级锁
- 轻量级锁: 这个锁操作都是比较消耗 CPU 的 如果能够比较快速的拿到锁, 多消耗点 CPU 也不亏
- 但是随着竞争激烈, 即使前一个线程释放锁, 也不一定能拿到锁, 啥时候能拿到, 时间可能会比较久, 所以没必要一直占用CPU资源
3. synchronized 的其它优化策略
3.1 锁消除
- 编译器, 会智能的判定, 当前这个代码, 是否有必要加锁
- 如果你写了加锁, 但实际上没有必要加锁, 就会把加锁操作自动删除掉
- 程序员也不能全指望考优化提高代码效率, 咱们自己也得发挥一些作用, 判断何时加锁, 也是咱们一个非常重要的工作
3.2 锁的粒度
概念
- 加锁操作里面包含的实际要执行的代码越多, 就认为锁的粒度越大
- 有时候, 希望锁的粒度小比较好, 并发程度高
- 有时候, 也希望锁的粒度大比较好 – 因为加锁解锁本身也有开销
JVM 对锁的粒度的优化
- 实际开发过程中, 使用细粒度锁, 是期望释放锁的时候其他线程能使用锁.
- 但是实际上可能并没有其他线程来抢占这个锁. 这种情况 JVM 就会自动把锁粗化, 避免频繁申请释放锁.
4. 总结
可以看到, synchronized 的策略是比价复杂的, 在背后做了很多事情, 目的为了让程序猿哪怕啥都不懂, 也不至于写出特别慢的程序.