synchronized和ReentrantLock傻傻分不清楚

程序员小迷 2024-04-21 16:04:18

synchronized和ReentrantLock都是用于线程间同步的机制,都是可重入锁(同一个线程可以多次获取同一个锁),它们的异同点如下:

一、应用场景

1.synchronized可应用于实例方法、静态方法和代码块。

2.ReentrantLock 是 java.util.concurrent.locks 包下的一个具体类,实现了 Lock 接口。使用时需要显式创建 ReentrantLock 对象并调用其方法。

二、锁获取与释放机制

1.当进入或退出同步代码时,synchronized自动获取锁释放锁,执行完毕或抛出异常时自动释放锁。

2.ReentrantLock需要手动调用lock()获取锁,调用unlock()释放锁。若没有主动释放锁,可能导致死锁。推荐使用 try-catch-finally 或 try-with-resources 结构来确保锁的释放。

三、尝试非阻塞地获取锁:

1.synchronized 无法做到尝试非阻塞地获取锁。

2.ReentrantLock 提供了tryLock()方法,该方法尝试获取锁,如果成功则返回true,否则立即返回false,线程不会被阻塞。

四、锁的公平性

1.synchronized是非公平的,即在锁被释放时,任何一个等待锁的线程都有机会获得锁。

2.ReentrantLock默认情况下也是非公平的,但可以通过带布尔值为true的构造函数构造成公平锁。当ReentrantLock被配置为公平锁时,则多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

五、条件等待与通知

1.synchronized 通过 Object 类的 wait(), notify(), notifyAll() 方法来实现线程间的条件等待与通知。这些方法必须在 synchronized 代码块或synchronized修饰的方法中调用,否则会抛出 IllegalMonitorStateException。

2.ReentrantLock 可以通过 newCondition() 方法创建多个条件变量 Condition 对象。线程可以调用 condition.await() 进行等待,其他线程调用 condition.signal() 或 condition.signalAll() 进行通知。这种方式支持更精细的线程间协作,可以避免"伪唤醒"问题,使得线程等待特定条件而不是仅仅等待锁的释放。

六、可中断性

1.synchronized不支持正在等待锁的线程被中断。

2.ReentrantLock提供了lockInterruptibly()方法支持等待锁的线程被中断(若所在的线程被中断,则会抛出异常并释放当前获得的锁)。当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。

七、可配置性

1.在synchronized中,锁对象wait()、notify()或notifyAl()只能关联一个隐含的条件Condition,若要和多于一个的条件Condition关联不得不额外地添加一个锁。不可设置超时时间。

2.一个ReentrantLock对象可以使用tryLock(long timeout, TimeUnit unit)设置超时时间(超时后线程不会一直阻塞,而是立即返回一个布尔值表示是否成功获取锁),可以使用getOwner()或isHeldByCurrentThread()判断锁是否被本线程或其他线程持有,可以使用getQueuedThreads()获取等待此锁的线程集合,可以使用getWaitingThreads(Condition condition)获取等待在此锁上的某个Condition上的线程集合 ,可以通过多次调用newCondition()同时绑定多个Condition对象实现线程等待/通知机制。

八、性能

在高并发场景下,ReentrantLock的性能可能优于synchronized,因为它提供了更多的灵活性和控制选项。

九、锁优化机制

1.synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、重量级锁。

2.ReentrantLock 的实现则是通过利用 CAS(Compare And Swap)自旋机制保证线程操作的原子性和 volatile 保证数据可见性以实现锁的功能。

十、引申

1.synchronized 是JVM层面的同步,JVM会优化其性能,例如锁消除、锁粗化等。

2.ReentrantLock 是一个类,可以扩展,例如 ReentrantReadWriteLock 就是在 ReentrantLock 基础上实现的读写锁,提供了更复杂的读写权限控制。

3.synchronized在JVM中是采用 monitorenter 和 monitorexit 两个指令来实现同步的,monitorenter 指令相当于加锁,monitorexit 相当于释放锁。而 monitorenter 和 monitorexit 就是基于 Monitor 实现的。

4.ReentrantLock的常用的方法如下:

tryLock():尝试获取锁

getHoldCount():查询当前线程执行 lock() 方法的次数

getQueueLength():返回正在排队等待获取此锁的线程数

isFair():该锁是否为公平锁

hasQueuedThread(Thread thread):返回指定的线程是否在等待获取此锁

5.ReentrantLock中lock() 和 lockInterruptibly() 的区别:

获取锁的过程中如果所在的线程中断,lock() 会忽略异常继续等待获取锁,而 lockInterruptibly() 则会抛出 InterruptedException 异常。

6.synchronized实现锁升级的过程:

在锁对象的对象头里面有一个 ThreadID 字段,在第一次访问的时候 ThreadID 为空,然后JVM让其持有偏向锁,并将 ThreadID 设置为调用锁对象的线程 ID,再次进入的时候会先判断 ThreadID 是否与其线程 ID 一致,如果一致则可以直接使用,如果不一致,则升级偏向锁为轻量级锁,通过自旋来获取锁,不会阻塞,执行一定次数之后会升级为重量级锁(映射到操作系统提供的互斥锁Mutex Lock上)。

7.可以通过设置JVM参数UseHeavyMonitors禁用偏向锁和轻量级锁,直接使用重量级锁。

微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。我是程序员小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

欢迎关注。助您在编程路上越走越好!

0 阅读:3