JAVA的对象锁

JAVA线程调度模式

在谈及JAVA锁之前,不如先去了解一下JAVA线程的调度模式吧,线程调度模式一般分为两种,抢占式调度和协同式调度,抢占式调度是由系统在某种算法的运行下给所有线程CPU的时间切片,这个时候所有的线程都会在分配到的时间内进行自己的操作,但是优先级低的线程可能就没有得到分配,所谓并行也只不过是轮流占用CPU资源,并没有达到在单核中同时执行多个线程,协同式调度是由线程完成后主动将CPU让给下一个线程,而JAVA使用的是抢占式调度,根据线程优先级来分配CPU时间片

JAVA的对象头

在这里简单介绍下JAVA对象锁实现的机制,其实现是依靠JAVA对象头,JAVA对象头中存放着对象的哈希值、锁信息、对象的年龄以及对象的元数据等信息,对象头的长度为2字来存储




































锁状态 存放内容 锁标志位(2bit)
无锁 对象的HashCode(25bit)+分代年龄(4bit)+是否是偏向锁(1bit) 01
偏向锁 线程ID(23bit)+时间标记Epoch(2bit)+分代年龄(4bit)+是否是偏向锁(1bit) 01
轻量锁 栈中锁记录指针(30bit) 00
重量锁 珍重互斥量的指针(30bit) 10
GC标记 空(30bit) 11

JAVA的锁

每一个JAVA对象都可以作为锁,JAVA对象锁等级分为4种,由低到高分别是无锁、偏向锁、轻量锁、重量锁

偏向锁

拥有同步锁的对象在同步时只需要检查偏向线程ID和当前线程ID是否一致

场景总是由同一个线程来持有这个对象锁,很少发生竞争

加锁发生在第一次进入同步代码块的时候,会使用CAS操作记录下当前线程的ID,并改变锁标志位

升级如果发现当前线程和偏向线程不一致,则发生了锁竞争,需要膨胀为轻量级锁

解锁发生在第一次出现锁竞争的时候,线程会等待全局安全点的到来,暂停偏向线程,查看该线程目前是否已经退出了同步块来决定是释放还是升级,然后唤醒偏向线程

轻量锁

轻量锁在每次进入和退出同步代码块的时候都需要使用CAS操作来更新对象头,每次都需要加锁和释放锁

场景由少量线程进行资源的争夺

加锁在MarkWord里记录当前线程的指针,使用CAS操作修改锁标示为到00

升级线程在多次尝试占用轻量级锁失败之后,膨胀为重量级锁,将锁释放并且等待线程唤醒,修改所标记为10,并唤醒线程,重新争夺锁

解锁使用CAS操作将锁为置为无锁

重量锁

重量级锁每次进入和退出同步代码块的时候都需要进行竞争,竞争不到的线程进入堵塞并等待唤醒

场景进行锁争夺的线程非常多

加锁为当前线程分配锁定记录,将对象头指向该记录

解锁操作对象头到无锁,唤醒等待该锁的所有线程