java 锁机制

88 阅读4分钟
  1. 锁需要解决什么问题
  • 并发环境下,多个线程会竞争同一份资源,导致数据不一致
  1. JVM模型

image.png

  • 红色区域不会出现线程竞争的问题,线程安全
  • 蓝色区域会被共享,线程不安全
  • java堆存放的是对象
  • 方法区存放的是类信息、常量、静态变量
  1. java对象
  • 每个对象都拥有一把锁,这把锁存放在对象头中,锁中记录了当前对象被哪个线程占用
  1. 对象的结构
  • 对象头
    • 存放了运行时的信息
    • 相较于实例数据属于额外的存储开销,因此被设计的很小
  • 实例数据
    • 存放了属性、方法
  • 对齐填充字节
    • 是为了满足java对象的大小必须是8bit的倍数而设计的
    • 一般无意义
  1. 对象头的结构
  • class point
    • 是一个指针,指向当前对象类型所在方法区中的类型数据
  • mark word
    • 存放了当前对象运行时和状态信息有关的数据

image.png

  1. synchronized(互斥锁)
  • java的synchronized,在编译后(javac),会生成monitor enter和monitor exit 两个字节码指令,使线程同步
  • monitor(管程、监视器)

image.png

  • synchronized的性能较低,因为是依赖monitorenter和monitorexit两个字节码指令,而monitor依赖于操作系统的mutex lock来实现的。java线程实际上是对操作系统线程的映射,每次挂起或者唤醒一个线程,都要在操作系统的用户态和内核台之间来回切换,这样的动作是比较重量级的,甚至切换的时间可能超过线程执行任务的时间
  • 但是从java6开始,引入了偏向锁、轻量级锁、重量级锁(这是锁的四个状态)
  • 由低到高是无锁、偏向锁、轻量级锁、重量级锁,刚好对应了mark word中的四个状态
  • 需要注意的是锁只能升级不能降级
  1. 无锁
  • 有两种情况
    1. 无竞争,加锁了但没有竞争
    2. 有竞争,但是不想用锁的方式,来同步线程。比如如果有多个线程想修改同一个值,不通过锁定资源的方式,同时只有一个线程能修改成功,其他修改失败的线程将不断重试直到成功,这就是CAS(Compare And Swap)。
  • CAS在操作系统中通过一条指令来实现,所以能保证原子性,通过诸如CAS的方式,我们可以进行无锁编程
  1. 偏向锁
  • 不使用synchronized的mutex lock、不使用CAS
  • 判断当前mark word中是否是偏向锁的标记,如果是则获取前23个bit,也就是线程ID,如果线程ID与当前线程ID一致,则当前线程获得该锁(锁偏爱这个线程,所以称为偏向锁)。如果此时发现有多个线程在竞争这个锁,则当前这个锁会升级为轻量级锁
  1. 轻量级锁
  • 当mark word最后2bit中的标识为00时,代表这是一个轻量级的锁,此时会在JVM线程栈中开辟一块lock record空间,该线程栈是私有的。在lock record中存储了mark word的副本和owner指针,其中owner指针指向该对象,该对象的mark record前30个bit存入指向jvm线程栈中对应的lock record指针,这样就完成了对象和锁之间的绑定
  • 此时如果有其他线程来竞争,其他线程会进入自旋,轮询当前锁是否被释放。又因为空转会导致cpu利用过多会导致CPU资源浪费,后续推出了适应性自旋,由上一次自旋时间和锁状态两个因素决定,让之前成功获得过锁的线程可以自旋更长的时间
  • 如果有cpu线程数一半或者10个以上的线程来竞争该锁,此时该锁会升级为重量级锁,也就是synchronized里的monitor,此时会完全锁定资源,切换到内核态去管控线程

image.png

  1. CAS
  • 乐观锁机制,每次都会去compare当前资源状态,本质上没有用到锁,是无锁编程,现在的cpu都一套默认的cas实现,方法是native修饰的,底层是汇编语言去调用的,CAS的核心原理是要保证原子性,例如AtomicInteger,就是典型的CAS实现。
  • CAS资源状态值 0-空闲 1-占用
  • 原理:A线程和B线程分别拥有两个值
    • old value 当前目标资源状态值
    • new value 想要将目标资源状态值更新后的值
  • 当A、B两个线程去争夺资源,会拿old value和最新的目标资源状态值比较,不一致则进入自旋,如果自旋次数超过10次(默认值或者自己设定一个自旋次数)后,就会放弃
  • 只能原子的去修改内存上的一个
  1. java的AQS框架 (太难了)

image.png