悲观锁、乐观锁
  • 悲观锁

    • 假设在修改数据时,同时有另外的线程修改数据
    • 处理之前加锁,数据操作完成后释放锁
  • 乐观锁

    • 假设在修改数据时,不会有其它的线程修改数据

    • 处理不加锁,同步数据时若数据已经被另外线程修改,则做失败或者重试处理

自旋锁
  • 若阻塞线程唤醒线程的开销大,可将线程进行循环等待
  • 实际是牺牲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保证原子性