java各种锁
悲观锁、乐观锁
悲观锁
- 假设在修改数据时,同时有另外的线程修改数据
- 处理之前加锁,数据操作完成后释放锁
乐观锁
假设在修改数据时,不会有其它的线程修改数据
处理不加锁,同步数据时若数据已经被另外线程修改,则做失败或者重试处理
自旋锁
- 若阻塞线程唤醒线程的开销大,可将线程进行循环等待
- 实际是牺牲CPU(处理器时间),换取线程切换的开销
问题:若一直无法抢占到运行条件,会一直自旋;解决方法控制自旋次数
公平锁、非公平锁
- 公平锁:线程按照申请锁的顺序存在队列中,只有队列第一个线程能获得锁,其它线程都是阻塞
- 非公平锁:线程也是按照申请锁的顺序存在队列中,但是刚来的线程若碰到正好有锁可用则能直接插列获得锁,正所谓来得早不如来得巧
公平锁:缺点是阻塞和唤醒线程开销无法避免,非公平锁对与来得巧的线程则无该开销
非公平锁:缺点是有些线程可能一直获取不到锁,或者等待到锁的时间很长
可重入锁、不可重入锁
- 可重入锁:同一个线程在外层方法获取锁的时候,调用该线程的内层方法会自动获取锁,避免出现死锁情况,在分布式锁中是会用一个字段来记录加锁的层数
- 不可重入锁:与可重入锁相反,同一个线程在外层方法获取锁的时候,调用该线程的内层方法不会获取到该锁
注:不可重入锁缺点:由外层方法获取锁的时候,若在该线程中有调用自己本线程方法的时候由于本线程未释放,则无法调用本线程方法,导致本线程无法释放,形成死锁
独享锁、共享锁
- 独享锁:获取到锁后,本线程独享读写
- 共享锁:获取到锁后,本线程读写,其它线程读
锁的状态
无锁、偏向锁、轻量级锁、重量级锁
锁只能升级不能降级,方向为 无锁–>偏向锁–>轻量级锁–>重量级锁
- 无锁–>偏向锁 当同步代码一直被一个线程占用,无锁就会转为偏向锁
- 偏向锁–>轻量级锁 当有另外的一个线程加入进来,偏向锁会转为轻量级锁,该加入的线程会已自旋的方式进行循环等待
- 轻量级锁–>重量级锁 当又有一个线程或多个线程加入进来,轻量级锁会转为重量级锁,等待锁的线程会转为阻塞状态
操作系统的调用分为内核态和用户态
无锁、偏向锁、轻量级锁是在用户态运行的
重量级锁是在内核态运行的
偏向锁:减少对象下一次访问线程时再次获取锁过程
轻量级锁:当对象的获取有竞争时,线程都会以CAS自旋方式去抢占对象,抢到对象的线程会将线程id存在 mark word中
重量级锁:重量级锁是内核态的,当自旋次数达到设定的最大自旋次数,会升级为重量级锁,mark word中是指向互斥量的指针,重量级锁是互斥锁
注:轻量级锁–>重量级锁 原因:等待的线程太多了,将未获取到锁的线程放入 等待状态
锁消除
若对象中存在 加了synchronized同步字段的方法,若jvm判断该对象不可能被其它线程引用,则jvm会自动优化,会自动消除对象内部的锁 如:StringBuffer
锁粗化
若对象中存在 加了synchronized同步字段的方法,若该对象被多次加锁(循环),则jvm会将加锁操作粗化到循环外部
借鉴出处
synchronized
实现原理:java原生地实现锁,即是线程将线程id写入到对象地markword中,哪个线程写入了,就哪个线程抢到了锁
三个层面解析synchronized:
代码层面 即synchronized修饰方法或者代码块
字节码层面 被synchronized修饰的代码块,在字节码层面是 用monitorenter和monitorexit这两个命令来实现加锁和解锁的
汇编层面 即lock修饰,CAS的原理在此处的实现命令 cmpxchg,即比较改变,lock来修饰 cmpxchg保证原子性