Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

synchronized 和 lock 的区别

2018-08-24

synchronized 和 lock 的区别

类别 synchronized Lock
存在层次 java 关键字,在jvm层面实现 java实现,底层依赖cpu指令,是个接口
锁的释放 执行完毕同步代码或者抛出异常 主动的unlock锁
锁的获取 实例方法(this),静态方法(class),代码块 lock()【等待类似synchronized】,tryLock()【不等待】
锁的状态 不可获知 tryLcok()
锁的类型 非公平锁,不能区分读写 ReentrantLock 默认非公平锁,可设置未公平锁。通过sync 抽象内部类【底层是AQS】
中断特性 synchronized 会导致线程无限等待,不可中断 lockInterruptibly

相关知识点

synchronized实现原理:

  1. 对象监视器锁(monitorenter/monitorexit)被占用时就获取了锁
  2. 两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。
  3. 对象头里面Mark Word 就是synchronized所使用的锁

lock实现原理

  1. AQS

各种锁的特点

  • 自旋锁:

(1)前提:线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的 (2)定义:就是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

(3)场景:自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

  • 偏向锁

(1)前提:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

(2)场景:偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下(即线程A尚未执行完同步代码块,线程B发起了申请锁的申请),则一定会转化为轻量级锁或者重量级锁。

(3)目的:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令

(4)定义:一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作,说白了就是置个变量,如果发现为true则无需再走各种加锁/解锁流程。

  • 轻量级锁

(1)场景:当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁

(2)目的:引入轻量级锁的主要目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗

(3)前提:“对于绝大部分的锁,在整个生命周期内都是不会存在竞争的”,如果打破这个依据则除了互斥的开销外,还有额外的CAS操作,因此在有多线程竞争的情况下,轻量级锁比重量级锁更慢。

  • 重量级锁

(1)前提:Synchronized是通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。

其他

  • Mark Word用于存储对象自身的运行时数据,如:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。

  • 对象头的MarkWord中的Lock Word指向Lock Record的起始地址,同时Lock Record中有一个Owner字段存放拥有该锁的线程的唯一标识(或者object mark word),表示该锁被这个线程占用
  • CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。
  • 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。
  • JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。多线程环境下,synchronized的吞吐量下降的非常严重,而ReentrankLock则能基本保持在同一个比较稳定的水平上。 到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

作者:aoho 链接:https://juejin.im/post/5a43ad786fb9a0450909cb5f 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


上一篇 spring知识点

下一篇 threadLocal详解

评论