synchronized与lock

Java存在synchronized和lock两种锁机制。synchronized是JVM层面的内置特性,采用悲观锁机制,适合资源竞争不激烈场景;lock是基于JDK层面的接口,使用乐观锁方式,适合高并发场景。文章还对比了两者优缺点、不同点和相同点,并给出何时用ReentrantLock代替synchronized的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java存在两种锁机制:synchronized和lock

synchronized

介绍

synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,通过对对象的头文件来操作,从而达到加锁和释放锁的目的。当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。

通常,对于synchronize(lock){…}这样的代码块,编译后会生成monitorenter和monitorexit指令,线程执行到monitorenter指令时会尝试取得lock对应的monitor的所有权(CAS设置对象头),取得后即获取到锁,执行monitorexit指令时会释放monitor的所有权即释放锁;

底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。
偏向锁—>轻量锁—>重量锁

悲观锁

Synchronized采用了悲观锁的机制,顾名思义,它假设每次执行同步的时候,别的线程总会也去执行,所以每次都会上锁。线程获得的是独占锁,意味着其他线程只能依靠阻塞来等待占有线程来释放锁。而CPU阻塞和唤醒线程会引起上下文切换,大量线程竞争锁的时候,导致低效。

使用

synchronized可以对整个方法同步,也可以对方法的部分代码块进行同步。当synchronized修饰方法的时候,synchronized是对象级的同步,意思就是说对于某个对象里面的被synchronized修饰的多个方法和synchronized(this)的代码块,当某一个线程访问一个synchronized修饰的方法或执行synchronized(this)的代码块,其它线程访问该对象内的synchronized修饰的方法或执行synchronized(this)的代码块将会处于等待状态,当之前得到锁的线程执行完方法或代码块时,其它线程才可以访问。

使用场景

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好

lock

介绍

lock是基于jdk层面实现的接口,和虚拟机层面不是一个概念;在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS)

乐观锁

Lock使用了乐观锁的方式,顾名思义,就是每次不加锁而是假设没有冲突而去完成操作。它是基于CAS操作的(Compare And Swap),通过竞争Lock内置队列同步器(AQS)中的状态位state,填充入自己的线程来获得锁。 现代CPU提供了指令,可以自动更新共享数据,而且能够检测其他线程的干扰,而compareAndSet()就用这些代替了锁定,这是一个自旋而非阻塞的算法,不会被挂起或者影响其他线程。

使用

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

使用场景

由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。

区别

synchronized

优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛
缺点:悲观的排他锁,不能进行高级功能

lock

优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁  
缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪

不同点

synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

相同点 

都是可重入锁

什么时候选择用 ReentrantLock 代替 synchronized

摘自于IBM网站上的一篇文章:
在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值