锁
-
java线程阻塞的代价 (理解java中各种锁的优缺点的基础)
- java的线程是映射到操作系统原生线程之上:阻塞或唤醒线程->操作系统介入->在户态与核心态之间切换
这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。
- 线程状态切换是高频操作时,会消耗很多CPU处理时间;
- 对于需要同步的简单的代码块,获取锁挂起操作消耗的时间>用户代码执行时间
synchronized会导致争用不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。
引自:https://www.cnblogs.com/linghu-java/p/8944784.html
-
乐观锁&悲观锁
- 乐观锁:认为读多写少
- 实现思路:比较跟上一次的版本号,如果一样则更新(redis也是,写操作更新版本)
- CAS 实现:CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,
要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值
V修改为B,否则什么都不做
- 悲观锁:认为写多,遇到并发写的可能性高 Synchronized、RetreenLock
- AQS框架下的锁:先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁
- 乐观锁:认为读多写少
-
自旋锁
- 内核态和用户态之间的切换进入阻塞挂起状态->线程只需要等一等(自旋,占用CPU做无用功)
- 适用场景:锁的竞争不激烈,且占用锁时间非常短
- 自旋的周期:避免自旋执行时间太长,自旋状态占用 CPU 资源
-
可重入锁(递归锁)
-
ReadWriteLock 读写锁
- 读锁
代码只读数据,可以很多人同时读,但不能同时写,那就上读锁 - 写锁
如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。 - 接 口 java.util.concurrent.locks.ReadWriteLock 具 体实现:ReentrantReadWriteLock
- 读锁
-
共享锁&独占锁
-
独占锁:每次只能有一个线程能持有锁,如Reentrantlock
悲观保守的加锁策略,它避免了读/读冲突(并不会影响数据的一致性,限制了不必要的并发性)
-
共享锁:乐观锁,允许多个线程同时获取锁(放宽了加锁策略)
java 的并发包中 ReadWriteLock(读-写锁)它允许一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行
-
-
重量级锁&轻量级锁
- 重量级锁 ( Mutex Lock ):
- 底层的操作系统的 Mutex Lock -> 监视器锁(monitor)->Synchronized (->实现)
- 操作系统实现线程之间的切换,需要从用户态转换到核心态
- 重量级锁:这种依赖于操作系统 Mutex Lock 所实现的锁
- 轻量级锁
- 锁的状态(4种):无锁状态、偏向锁、轻量级锁和重量级锁 (锁的升级是单向的,->)
- 应用场景?:线程交替执行同步块
- 不适用场景?:同一时间访问同一锁,则轻量级锁膨胀为重量级锁
- 重量级锁 ( Mutex Lock ):
-
偏向锁
-
适用场景:始终只有一个线程在执行同步块,锁无竞争
一旦有竞争就升级为轻量级锁,撤销偏向锁,导致stop the word操作
-