@@ -646,16 +646,102 @@ public class SynchronizedDemo {
646
646
647
647
相比` synchronized ` ,` ReentrantLock ` 增加了一些高级功能。主要来说主要有三点:
648
648
649
- - ** 等待可中断** : ` ReentrantLock ` 提供了一种能够中断等待锁的线程的机制,通过 ` lock.lockInterruptibly() ` 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情 。
649
+ - ** 等待可中断** : ` ReentrantLock ` 提供了一种能够中断等待锁的线程的机制,通过 ` lock.lockInterruptibly() ` 来实现这个机制。也就是说当前线程在等待获取锁的过程中,如果其他线程中断当前线程「 ` interrupt() ` 」,当前线程就会抛出 ` InterruptedException ` 异常,可以捕捉该异常进行相应处理 。
650
650
- ** 可实现公平锁** : ` ReentrantLock ` 可以指定是公平锁还是非公平锁。而` synchronized ` 只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。` ReentrantLock ` 默认情况是非公平的,可以通过 ` ReentrantLock ` 类的` ReentrantLock(boolean fair) ` 构造方法来指定是否是公平的。
651
651
- ** 可实现选择性通知(锁可以绑定多个条件)** : ` synchronized ` 关键字与` wait() ` 和` notify() ` /` notifyAll() ` 方法相结合可以实现等待/通知机制。` ReentrantLock ` 类当然也可以实现,但是需要借助于` Condition ` 接口与` newCondition() ` 方法。
652
+ - ** 支持超时** :` ReentrantLock ` 提供了 ` tryLock(timeout) ` 的方法,可以指定等待获取锁的最长等待时间,如果超过了等待时间,就会获取锁失败,不会一直等待。
652
653
653
654
如果你想使用上述功能,那么选择 ` ReentrantLock ` 是一个不错的选择。
654
655
655
656
关于 ` Condition ` 接口的补充:
656
657
657
658
> ` Condition ` 是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个` Lock ` 对象中可以创建多个` Condition ` 实例(即对象监视器),** 线程对象可以注册在指定的` Condition ` 中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用` notify()/notifyAll() ` 方法进行通知时,被通知的线程是由 JVM 选择的,用` ReentrantLock ` 类结合` Condition ` 实例可以实现“选择性通知”** ,这个功能非常重要,而且是 ` Condition ` 接口默认提供的。而` synchronized ` 关键字就相当于整个 ` Lock ` 对象中只有一个` Condition ` 实例,所有的线程都注册在它一个身上。如果执行` notifyAll() ` 方法的话就会通知所有处于等待状态的线程,这样会造成很大的效率问题。而` Condition ` 实例的` signalAll() ` 方法,只会唤醒注册在该` Condition ` 实例中的所有等待线程。
658
659
660
+ 关于 ** 等待可中断** 的补充:
661
+
662
+ > ` lockInterruptibly() ` 会让获取锁的线程在阻塞等待的过程中可以响应中断,即当前线程在获取锁的时候,发现锁被其他线程持有,就会阻塞等待
663
+ >
664
+ > 在阻塞等待的过程中,如果其他线程中断当前线程 「 ` interrupt() ` 」,就会抛出 ` InterruptedException ` 异常,可以捕获该异常,做一些处理操作
665
+ >
666
+ > 为了更好理解这个方法,借用 Stack Overflow 上的一个案例,可以更好地理解 ` lockInterruptibly() ` 可以响应中断:
667
+ >
668
+ > ``` JAVA
669
+ > public class MyRentrantlock {
670
+ > Thread t = new Thread () {
671
+ > @Override
672
+ > public void run () {
673
+ > ReentrantLock r = new ReentrantLock ();
674
+ > // 1.1、第一次尝试获取锁,可以获取成功
675
+ > r. lock();
676
+ >
677
+ > // 1.2、此时锁的重入次数为 1
678
+ > System . out. println(" lock() : lock count :" + r. getHoldCount());
679
+ >
680
+ > // 2、中断当前线程,通过 Thread.currentThread().isInterrupted() 可以看到当前线程的中断状态为 true
681
+ > interrupt();
682
+ > System . out. println(" Current thread is intrupted" );
683
+ >
684
+ > // 3.1、尝试获取锁,可以成功获取
685
+ > r. tryLock();
686
+ > // 3.2、此时锁的重入次数为 2
687
+ > System . out. println(" tryLock() on intrupted thread lock count :" + r. getHoldCount());
688
+ > try {
689
+ > // 4、打印线程的中断状态为 true,那么调用 lockInterruptibly() 方法就会抛出 InterruptedException 异常
690
+ > System . out. println(" Current Thread isInterrupted:" + Thread . currentThread(). isInterrupted());
691
+ > r. lockInterruptibly();
692
+ > System . out. println(" lockInterruptibly() --NOt executable statement" + r. getHoldCount());
693
+ > } catch (InterruptedException e) {
694
+ > r. lock();
695
+ > System . out. println(" Error" );
696
+ > } finally {
697
+ > r. unlock();
698
+ > }
699
+ >
700
+ > // 5、打印锁的重入次数,可以发现 lockInterruptibly() 方法并没有成功获取到锁
701
+ > System . out. println(" lockInterruptibly() not able to Acqurie lock: lock count :" + r. getHoldCount());
702
+ >
703
+ > r. unlock();
704
+ > System . out. println(" lock count :" + r. getHoldCount());
705
+ > r. unlock();
706
+ > System . out. println(" lock count :" + r. getHoldCount());
707
+ > }
708
+ > };
709
+ > public static void main (String str []) {
710
+ > MyRentrantlock m = new MyRentrantlock ();
711
+ > m. t. start();
712
+ > }
713
+ > }
714
+ > ```
715
+ >
716
+ > 输出:
717
+ >
718
+ > ```BASH
719
+ > lock() : lock count : 1
720
+ > Current thread is intrupted
721
+ > tryLock() on intrupted thread lock count : 2
722
+ > Current Thread isInterrupted: true
723
+ > Error
724
+ > lockInterruptibly() not able to Acqurie lock: lock count : 2
725
+ > lock count : 1
726
+ > lock count : 0
727
+ > ```
728
+
729
+ 关于 ** 支持超时** 的补充:
730
+
731
+ > ** 为什么需要 `tryLock(timeout)` 这个功能呢?**
732
+ >
733
+ > 假设这样一种场景:有一个加载缓存数据的任务在某个时间点多个线程同时要来执行,为了并发安全,通过锁来控制只有一个线程可以执行该任务。
734
+ >
735
+ > 假设大量线程同时来执行该任务,由于需要穿行执行,因此大量线程都进入阻塞队列等待获取锁
736
+ >
737
+ > 当第一个线程拿到锁,执行完任务之后,此时后边的线程都不需要执行该任务了,但是由于没有这个超时功能,导致后边的线程还需要在队列中阻塞等待获取锁,再一个个进入同步代码块,发现任务已经执行过了,不需要自己再执行了,之后再退出释放锁,退出同步代码块。
738
+ >
739
+ > 因此就需要一个支持超时的功能,`tryLock(timeout)` 的作用就是 ** 将大量线程的串行操作转为并行操作** ,当大量线程等待时间已经超过了指定的超时时间,直接返回 false ,表示获取锁失败,不需要大量的线程串行排队等待获取锁。
740
+ >
741
+ > ! [image- 20241208153800259 ](https: // 11laile-note-img.oss-cn-beijing.aliyuncs.com/image-20241208153800259.png)
742
+ >
743
+ > 这里 `tryLock(timeout)` 的情况只是举一个特殊的情况,其实是参考了分布式环境下,更新 Redis 缓存时会出现这种情况,但是在分布式环境下肯定不会使用 synchronized ,因此这里主要是举个例子说一下 tryLock(timeout) 的作用!
744
+
659
745
### 可中断锁和不可中断锁有什么区别?
660
746
661
747
- ** 可中断锁** :获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。`ReentrantLock ` 就属于是可中断锁。
0 commit comments