synchronized和lock区别
区别类型 | synchronized | Lock |
存在层次 | Java的关键字,在jvm层面上 | 是JVM的一个接口 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁) |
锁的释放 | 1、获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁类型 | 锁可重入、不可中断、非公平 | 可重入、可判断 可公平(两者皆可) |
性能 | 少量同步 | 适用于大量同步 |
支持锁的场景 | 1. 独占锁 | 1. 公平锁与非公平锁 |
synchronized的 加锁流程
- 由于HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同
一线程多次获得,为了让线程获得锁的代价更低从而引入偏向锁。偏向锁降低了锁重入的代价。偏向锁在获取资源的时候会在锁对象头上记录当前线程ID,偏向锁并不会主动释放,这样每次偏向锁进入的时候都会判
断锁对象头中线程ID是否为自己,如果是当前线程重入,直接进入同步操作,不需要额外的
操作。默认在开启偏向锁和轻量锁的情况下,当线程进来时,首先会加上偏向锁,其实这里只
是用一个状态来控制,会记录加锁的线程,如果是线程重入,则不会进行锁升级。 - 获取偏向锁流程:
- 判断是否为可偏向状态--MarkWord中锁标志是否为‘01’,是否偏向锁是否为‘1’
- 如果是可偏向状态,则查看线程ID是否为当前线程,如果是,则进入步骤 'V',否则进入
步骤‘c’
- 如果是可偏向状态,则查看线程ID是否为当前线程,如果是,则进入步骤 'V',否则进入
- 通过CAS操作竞争锁,如果竞争成功,则将MarkWord中线程ID设置为当前线程ID,然
- 后执行‘e’;竞争失败,则执行‘d’
- CAS获取偏向锁失败表示有竞争。当达到safepoint时获得偏向锁的线程被挂起,偏向锁
升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块
- 执行同步代码
- 轻量级锁是相对于重量级锁需要阻塞/唤醒涉及上下文切换而言,主要针对多个线程在不同时
间请求同一把锁的场景。轻量级锁获取过程: - 进行加锁操作时,jvm会判断是否已经是重量级锁,如果不是,则会在当前线程栈帧中划
- 出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中
- 复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁
记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘c’,否则执行‘d’
- 更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此
- 对象处于轻量级锁状态
- 更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执
行‘e’,否则执行‘f’
- 表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word) 为null,并指向Mark Word的锁对象,起到一个重入计数器的作用
- 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值
仍未获取到锁,则升级为重量级锁
- 表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值
- 当有多个锁竞争轻量级锁则会升级为重量级锁,重量级锁正常会进入一个cxq的队列,在调用
wait方法之后,则会进入一个waitSet的队列park等待,而当调用notify方法唤醒之后,则有
可能进入EntryList。重量级锁加锁过程:
- 分配一个ObjectMonitor对象,把Mark Word锁标志置为‘10’,然后Mark Word存储指向ObjectMonitor对象的指针。ObjectMonitor对象有两个队列和一个指针,每个需要获取锁的线程都包装成ObjectWaiter对象
- 多个线程同时执行同一段同步代码时,ObjectWaiter先进入EntryList队列,当某个线程
- 获取到对象的monitor以后进入Owner区域,并把monitor中的owner变量设置为当前线
程同时monitor中的计数器count+1;
- 获取到对象的monitor以后进入Owner区域,并把monitor中的owner变量设置为当前线
为什么synchronized实现锁升级之后为什么提高了效率
synchronized锁升级的流程:无锁->偏向锁->轻量级锁->重量级锁
无锁的性能最高,不做赘述。
偏向锁:只需要走一次对象头上的markword判断,没有CAS,没有自旋,没有阻塞(线程阻塞会从用户态切换到
内核态)
轻量级锁:当偏向锁发送了CAS而没有申请到锁后,升级为轻量级锁。轻量级锁没有自旋,没有阻塞
重量级锁:当轻量级锁自旋后没有申请到锁后会升级为重量级锁。线程向cpu申请锁资源,没有升级到的线
程会阻塞并进入等待队列,会发生频繁的状态切换。
总结:偏向锁是为了避免CAS,在对象头上就把加锁问题解决。轻量级锁和自旋锁是为了避免线程进入内核的阻塞状态,这对性能非常不利;重量级锁是终极方案,避免自旋锁的空耗cpu而没有产生实际工作。