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操作将锁为置为无锁
重量锁
重量级锁每次进入和退出同步代码块的时候都需要进行竞争,竞争不到的线程进入堵塞并等待唤醒
场景进行锁争夺的线程非常多
加锁为当前线程分配锁定记录,将对象头指向该记录
解锁操作对象头到无锁,唤醒等待该锁的所有线程