基本使用回顾
synchronized 【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他线程再想获取这个【对象锁】时就会阻塞住。
public class TicketDemo{
static Object lock = new Object();
int ticketNum = 10;
public void getTicket(){
synchronized(lock){
if(ticketNum <= 0){
return ;
}
System.out.println(Thread.currentThread().getName()+"抢到一张票,剩余:"+ticketNum);
// 非原子性操作
ticketNum--;
}
}
public static void main(String[] args){
TicketDemo ticketDemo = new TicketDemo();
for(int i =0;i< 20;i++){
new Thread(()->{
ticketDemo.getTicket();
}).start();
}
}
}
Monitor
public class SyncTest {
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized(lock){
counter++;
}
}
}
通过 javap -v xx.class 查看 class 字节码信息。
第二次释放对象锁是因为底层使用了一个隐式的 tryfinally,防止上锁操作出现异常,不能及时释放锁。
Monitor 被翻译为监视器,是由 jvm 提供的,c++语言实现。
当一个线程进入 synchronized 代码块之后,会让对象锁与 Monitor 进行关联,检查一下 Monitor 中的 Owner 是否为 null,如果为 null,则让当前线程持有,如果不为 null,则需要到 EntryList 中进行等待,如果线程调用了 wait 方法,则会进入到 WaitSet 里面。
-
Owner:存储当前获取锁的线程,只能有一个线程可以获取。
-
EntryList:关联没有抢到锁的线程,处于 Blocked 状态的线程。
-
WaitSet:关联调用了 wait 方法的线程,处于 Waiting 状态的线程。
面试回答参考synchronized 关键字的底层原理 -
synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】。
-
它的底层由 monitor 实现的,monitor 是 jvm 级别的对象(C++实现),线程获得锁需要使用对象(锁)关联 monitor。
-
在 monitor 内部有三个属性,分别是 owner,entryList,waitset。
-
其中 Owner 是关联的获得锁的线程,并且只能关联一个线程;EntryList 关联的是处于阻塞状态的线程;WaitSet 关联的是处于 waiting 状态的线程。
synchronized 关键字的底层原理-进阶
Monitor 实现的锁属于重量级锁,你了解过锁升级吗?
- Monitor 实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
- 在 JDK1. 6 引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。
Java 中的 synchronized 有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。
描述 | |
---|---|
重量级锁 | 底层使用的 Monitor 实现,里面涉及到了用户态和内核态的切换,进程的上下文切换,成本较高,性能比较低。 |
轻量级锁 | 线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是 CAS 操作,保证原子性。 |
偏向锁 | 一段很长的时间内都只被一个线程使用锁,可以使用偏向锁,在第一次获取锁时,会有一个 CAS 操作,之后该线程再获取锁,只需要判断 markword 中是否是自己的线程 id 即可,而不是开销相对较大的 CAS 命令。 |