🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## **ReentrantLock** ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完 成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等 避免多线程死锁的方法。 ### **Lock接口的主要方法** 1. **void lock()**: 执行此方法时,如果锁处于空闲状态 , 当前线程将获取到锁. 相反 , 如果锁已经 被其他线程持有, 将禁用当前线程 , 直到当前线程获取到锁 . 2. **boolean tryLock()** :如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. 该方法和 lock()的区别在于, tryLock() 只是"试图"获取锁 , 如果锁不可用, 不会导致当前线程被禁用 , 当前线程仍然继续往下执行代码 . 而 lock()方法则是一定要获取到锁 , 如果锁不可用 , 就一 直等待, 在未获得锁之前 ,当前线程并不继续向下执行 . 3. **void unlock()**:执行此方法时 ,当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程 并不持有锁, 却执行该方法, 可能导致异常的发生 . 4. **Condition newCondition()** :条件对象,获取等待通知组件。该组件和当前的锁绑定, 当前线程只有获取了锁,才能调用该组件的 await() 方法,而调用后,当前线程将缩放锁。 5. **getHoldCount()** :查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次 数。 6. **getQueueLength()**:返回正等待获取此锁的线程估计数,比如启动 10 个线程,1 个 线程获得锁,此时返回的是 9 7. **getWaitQueueLength**:( Condition condition)返回等待与此锁相关的给定条件的线 程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了 condition 对象的 await 方法,那么此时执行此方法返回 10 8.** hasWaiters(Condition condition)** :查询是否有线程等待与此锁有关的给定条件 (condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法 9. **hasQueuedThread(Threadthread)**:查询给定线程是否等待获取此锁 10.**hasQueuedThreads()**:是否有线程等待此锁 11.isFair():该锁是否公平锁 12.**isHeldByCurrentThread()** :当前线程是否保持锁锁定,线程的执行 lock 方法的前后分 别是 false 和 true 13.**isLock()**:此锁是否有任意线程占用 14.**lockInterruptibly (**):如果当前线程未被中断,获取锁 15.**tryLock()**:尝试获得锁,仅在调用时锁未被线程占用,获得锁 16.**tryLock(long timeout TimeUnit unit)**:如果锁在给定等待时间内没有被另一个线程保持, 则获取该锁。 ### **ReentrantLock用法** 重入锁(ReentrantLock)是一种递归无阻塞的同步机制。 ~~~java public class LockExample { private Lock lock = new ReentrantLock(); public void func() { lock.lock(); try { for (int i = 0; i < 10; i++) { System.out.print(i + " "); } } finally { lock.unlock(); // 确保释放锁,从而避免发生死锁。 } } } ~~~ ~~~java public static void main(String[] args) { LockExample lockExample = new LockExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> lockExample.func()); executorService.execute(() -> lockExample.func()); } ~~~ ~~~ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 ~~~ ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁,相比于 synchronized,它多了以下高级功能: **1\. 等待可中断** 当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。 **2\. 可实现公平锁** 公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。 synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。 **3\. 锁绑定多个条件** 一个 ReentrantLock 对象可以同时绑定多个 Condition 对象。 ### synchronized 和 ReentrantLock 比较 **1\. 锁的实现** synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。 **2\. 性能** 新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等。目前来看它和 ReentrantLock 的性能基本持平了,因此性能因素不再是选择 ReentrantLock 的理由。synchronized 有更大的性能优化空间,应该优先考虑 synchronized。 **3\. 功能** ReentrantLock 多了一些高级功能。 **4\. 使用选择** 除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。 ### synchronized与lock的区别,使用场景。看过synchronized的源码没? * (用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 * (用法)lock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally 块中写 unlock() 以防死锁。 * (性能)synchronized 是托管给 JVM 执行的,而 lock 是 Java 写的控制锁的代码。在 Java1.5 中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 Java1.6 ,发生了变化。synchronize 在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致 在 Java1.6 上 synchronize 的性能并不比 Lock 差。 * (机制)**synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁**。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。**Lock 用的是乐观锁方式**。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。