一、ReentrantLock 类
1.1 什么是reentrantlock
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢? 简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
1.2 ReentrantLock与 synchronized的比较
相同: ReentrantLock提供了synchronized类似的功能和内存语义。
不同:
(1)ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
(2)ReentrantLock 的性能比synchronized会好点。
(3)ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。
1.3 ReentrantLock扩展的功能
1.3.1 实现可轮询的锁请求
在内部锁中,死锁是致命的——唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误恢复机制,可以规避死锁的发生。
如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。此方法的典型使用语句如下:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}}
1.3.2 实现可定时的锁请求
当使用内部锁时,一旦开始请求,锁就不能停止了,所以内部锁给实现具有时限的活动带来了风险。为了解决这一问题,可以使用定时锁。当具有时限的活
动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long, TimeUnit)方法实现。
1.3.3 实现可中断的锁获取请求
可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。
1.4 ReentrantLock不好与需要注意的地方
(1) lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时bug,当有一天bug爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放
(2) 当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。
二、 条件变量 Condition
条件变量很大一个程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。
条件 (也称为 条件队列 或 条件变量 )为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是: 以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。
上述API说明表明条件变量需要与锁绑定,而且多个Condition需要绑定到同一锁上。前面的 Lock中提到,获取一个条件变量的方法是 Lock.newCondition() 。
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
以上是 Condition 接口定义的方法, await* 对应于 Object.wait , signal 对应于 Object.notify , signalAll 对应于 Object.notifyAll 。特别说明的是 Condition 的接口改变名称就是为了避免与Object中的 wait/notify/notifyAll 的语义和使用上混淆,因为Condition同样有 wait/notify/notifyAll 方法。
每一个 Lock 可以有任意数据的 Condition 对象, Condition 是与 Lock 绑定的,所以就有 Lock的公平性特性:如果是公平锁,线程为按照FIFO的顺序从 Condition.await 中释放,如果是非公平锁,那么后续的锁竞争就不保证FIFO顺序了。
一个使用Condition实现 生产者消费者 的模型例子如下。
import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @see ArrayBlockingQueue * @author fansxnet * * @param <T> */ public class ProductQueue<T> { private final T[] items; private final Lock lock; private Condition notFull; private Condition notEmpty; // private int head, tail, count; public ProductQueue(int maxSize, boolean fair) { items = (T[]) new Object[maxSize]; this.lock = new ReentrantLock(fair); this.notFull = this.lock.newCondition(); this.notEmpty = this.lock.newCondition(); } public ProductQueue() { this(10,false); } public void put(T t) throws InterruptedException { lock.lockInterruptibly(); try { while (count == getCapacity()) { notFull.await(); } items[tail] = t; if (++tail == getCapacity()) { tail = 0; } ++count; notEmpty.signalAll(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lockInterruptibly(); try { while (count == 0) { notEmpty.await(); } T ret = items[head]; items[head] = null;// GC // if (++head == getCapacity()) { head = 0; } --count; notFull.signalAll(); return ret; } finally { lock.unlock(); } } public int getCapacity() { return items.length; } public int size() { lock.lock(); try { return count; } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(10); final ProductQueue<Integer> queue = new ProductQueue<Integer>(); final CountDownLatch cdOrder = new CountDownLatch(1); for (int i = 0; i < 5; i++) { pool.submit(new Runnable() { public void run() { try { cdOrder.await(); System.out.println("start put "); while (true) { int c = new Random().nextInt(100); System.out.println("put" + c); queue.put(c); } } catch (InterruptedException e) { e.printStackTrace(); } } }); pool.submit(new Runnable() { public void run() { try { cdOrder.await(); System.out.println("start take "); while (true) { System.out.println("take" + queue.take()); } } catch (InterruptedException e) { e.printStackTrace(); } } }); } System.out.println("working"); cdOrder.countDown(); pool.shutdown(); } }
在这个例子中消费 take() 需要 队列不为空,如果为空就挂起( await() ),直到收到 notEmpty的信号;生产 put() 需要队列不满,如果满了就挂起( await() ),直到收到 notFull 信号。
参考
Java多线程(九)之ReentrantLock与Condition
相关推荐
Java并发编程中的ReentrantLock是Java并发包(java.util.concurrent,简称JUC)中的一个重要的锁机制,它是Lock接口的一个具体实现,提供了比synchronized更强大的锁定功能和更细粒度的控制。ReentrantLock的主要...
用CyclicBarrier,reentrantlock,condition来完成同时购买,同步购买的功能 JUC系列之模拟抢票(N人同时抢票,票不足系统补仓,N-M人继续抢票) http://blog.csdn.net/crazyzxljing0621/article/details/77891620
3. **JUC核心API**:重点学习JUC包中的核心API,如`ReentrantLock`、`Condition`、`CountDownLatch`等。 4. **实战项目**:通过实际项目练习来巩固所学知识,例如实现一个基于线程池的任务调度系统或一个高并发的...
8. Lock接口 (ReentrantLock 可重入锁) 特性 ReentantLock 继承接口 Lock 并实现了接口中定义的方法, 它是一种可重入锁, 除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁...
⼀、ReentrantLock重⼊锁 1.1> 概述 1.2> 中断响应 lockInterruptibly() 1.3> 锁申请等待限时 tryLock(long time, TimeUnit unit) 1.4> 公平锁和⾮公平锁 1.5> AQS源码解析 ⼆、Condition重⼊锁的搭配类 三、...
### JUC并发编程中的虚假唤醒及其解决方案 #### 一、虚假唤醒的概念与原理 在Java并发编程中,尤其是在处理多线程同步时,一个重要的概念是“虚假唤醒”(Spurious Wakeup)。这是一种较为罕见的现象,但在实际...
juc-demo JUC包下常用工具练习Demo 内容: 1、Semaphore 2、CountDownLatch 3、CyclicBarrier 4、ReentrantLock + Condition实现阻塞队列 Created by @minghui.y.
4. **锁机制**:JUC通过Lock接口(如ReentrantLock)和Condition接口提供了比synchronized更细粒度的锁控制。Lock接口支持可重入锁,可以精确控制线程的等待和唤醒,而Condition则可以实现线程间的条件等待和通知。 ...
### AQS核心原理与ReentrantLock的实现细节 #### AQS(AbstractQueuedSynchronizer)内部结构 AQS作为Java并发工具包(JUC)中的一个核心抽象类,其设计目的是为了实现各种同步器(如锁、信号量等)。AQS主要通过三...
Java的并发编程是多线程和多任务处理的核心技术之一,而在Java并发包 java.util.concurrent 中,AQS(AbstractQueuedSynchronizer)扮演了至关重要的角色。AQS是一种框架,用来构建锁或其他同步组件的基础。它提供了...
Java并发编程是Java程序员需要掌握的重要技能之一,Java并发库(Java Util Concurrency, JUC)为多线程编程提供了强大的支持。在这个"juc入门案例演示代码"中,我们将会探讨两个关键的JUC组件:`JUCLock`和`process_...
在示例中的 `Signal` 类中,可能包含一个 `Lock` 实例,如 `ReentrantLock`,以及对应的 `Condition` 对象,通过 `condition.await()` 和 `condition.signal()` 方法来实现线程间的协调通信。 总结来说,JUC 包为...
在本实例中,可能包括ReentrantLock(可重入锁)的示例,它比synchronized更灵活,可以进行更复杂的操作,如尝试加锁、公平锁与非公平锁的选择,以及锁的条件(Condition)管理。 3. **CountDownLatch**: ...
- **Lock接口**:ReentrantLock是其典型实现,提供公平锁、非公平锁以及更细粒度的控制。 - **volatile**:保证变量在多线程环境中的可见性,但不保证原子性。 5. **并发容器** - **ConcurrentHashMap**:线程...
- `Condition` 是与 `ReentrantLock` 关联的条件变量接口,提供比 `Object` 的 `wait` 和 `notify` 方法更强大的功能。 - **常用方法**: - `void await()`:使当前线程等待,直到被信号唤醒或其他线程中断。 - `...
- **Condition**:提供了比synchronized更细粒度的控制,可以有多个等待集,通过await()和signal()来控制线程的等待和唤醒。 - **Lock**:接口,提供了比synchronized更灵活的锁机制,可以实现更复杂的同步策略。 - ...
此外,AQS还支持条件变量(Condition),每个Condition对应一个单向链表,只有在使用条件变量时才会创建。 3. **核心方法**:AQS提供了几个关键的抽象方法,如`tryAcquire()`和`tryRelease()`用于独占模式的资源...
- Condition:与ReentrantLock配合使用,提供更灵活的条件等待机制。 - ReadWriteLock:读写锁,允许多个读线程同时访问,但写线程独占资源。 6. **并发工具类**: - CountDownLatch:一次性计数器,用于同步多...