*Lock ReentrantLock Condition
ReentrantLock可以实现synchronized关键字的功能,还可以实现停止等待线程执行,定时停止等待线程执行,通过投票获取锁等操作。在高并发条件下,相比Synchorized具有更好的性能表现。ReentrantLock类可以实现公平锁和不公平锁,默认情况下是不公平锁。不公平锁消耗的系统资源少,性能好于公平锁。
Lock方法提供了Condition来进行线程同步,Condition类包括await,signal,signalall方法对应于Object类提供的wait、notify、notifyall方法。
附件:
一、ReentrantLock 类
1.1 什么是reentrantlock
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 实现可中断的锁获取请求
1.4 ReentrantLock不好与需要注意的地方
二、条件变量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实现生产者消费者的模型例子如下。
[java] view plaincopy
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class ProductQueue {
- private final T[] items;
- private final Lock lock = new ReentrantLock();
- private Condition notFull = lock.newCondition();
- private Condition notEmpty = lock.newCondition();
- //
- private int head, tail, count;
- public ProductQueue(int maxSize) {
- items = (T[]) new Object[maxSize];
- }
- public ProductQueue() {
- this(10);
- }
- public void put(T t) throws InterruptedException {
- lock.lock();
- 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.lock();
- 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();
- }
- }
- }
在这个例子中消费take()需要 队列不为空,如果为空就挂起(await()),直到收到notEmpty的信号;生产put()需要队列不满,如果满了就挂起(await()),直到收到notFull的信号。
可能有人会问题,如果一个线程lock()对象后被挂起还没有unlock,那么另外一个线程就拿不到锁了(lock()操作会挂起),那么就无法通知(notify)前一个线程,这样岂不是“死锁”了?
2.1 await* 操作
上一节中说过多次ReentrantLock是独占锁,一个线程拿到锁后如果不释放,那么另外一个线程肯定是拿不到锁,所以在lock.lock()和lock.unlock()之间可能有一次释放锁的操作(同样也必然还有一次获取锁的操作)。我们再回头看代码,不管take()还是put(),在进入lock.lock()后唯一可能释放锁的操作就是await()了。也就是说await()操作实际上就是释放锁,然后挂起线程,一旦条件满足就被唤醒,再次获取锁!
[java] view plaincopy
- public final void await() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- Node node = addConditionWaiter();
- int savedState = fullyRelease(node);
- int interruptMode = 0;
- while (!isOnSyncQueue(node)) {
- LockSupport.park(this);
- if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
- break;
- }
- if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
- interruptMode = REINTERRUPT;
- if (node.nextWaiter != null)
- unlinkCancelledWaiters();
- if (interruptMode != 0)
- reportInterruptAfterWait(interruptMode);
- }
上面是await()的代码片段。上一节中说过,AQS在获取锁的时候需要有一个CHL的FIFO队列,所以对于一个Condition.await()而言,如果释放了锁,要想再一次获取锁那么就需要进入队列,等待被通知获取锁。完整的await()操作是安装如下步骤进行的:
- 将当前线程加入Condition锁队列。特别说明的是,这里不同于AQS的队列,这里进入的是Condition的FIFO队列。后面会具体谈到此结构。进行2。
- 释放锁。这里可以看到将锁释放了,否则别的线程就无法拿到锁而发生死锁。进行3。
- 自旋(while)挂起,直到被唤醒或者超时或者CACELLED等。进行4。
- 获取锁(acquireQueued)。并将自己从Condition的FIFO队列中释放,表明自己不再需要锁(我已经拿到锁了)。
这里再回头介绍Condition的数据结构。我们知道一个Condition可以在多个地方被await*(),那么就需要一个FIFO的结构将这些Condition串联起来,然后根据需要唤醒一个或者多个(通常是所有)。所以在Condition内部就需要一个FIFO的队列。
[java] view plaincopy
- private transient Node firstWaiter;
- private transient Node lastWaiter;
上面的两个节点就是描述一个FIFO的队列。我们再结合前面提到的节点(Node)数据结构。我们就发现Node.nextWaiter就派上用场了!nextWaiter就是将一系列的Condition.await*串联起来组成一个FIFO的队列。
2.2 signal/signalAll 操作
await*()清楚了,现在再来看signal/signalAll就容易多了。按照signal/signalAll的需求,就是要将Condition.await*()中FIFO队列中第一个Node唤醒(或者全部Node)唤醒。尽管所有Node可能都被唤醒,但是要知道的是仍然只有一个线程能够拿到锁,其它没有拿到锁的线程仍然需要自旋等待,就上上面提到的第4步(acquireQueued)。
[java] view plaincopy
- private void doSignal(Node first) {
- do {
- if ( (firstWaiter = first.nextWaiter) == null)
- lastWaiter = null;
- first.nextWaiter = null;
- } while (!transferForSignal(first) &&
- (first = firstWaiter) != null);
- }
- private void doSignalAll(Node first) {
- lastWaiter = firstWaiter = null;
- do {
- Node next = first.nextWaiter;
- first.nextWaiter = null;
- transferForSignal(first);
- first = next;
- } while (first != null);
- }
上面的代码很容易看出来,signal就是唤醒Condition队列中的第一个非CANCELLED节点线程,而signalAll就是唤醒所有非CANCELLED节点线程。当然了遇到CANCELLED线程就需要将其从FIFO队列中剔除。
[java] view plaincopy
- final boolean transferForSignal(Node node) {
- if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
- return false;
- Node p = enq(node);
- int c = p.waitStatus;
- if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
- LockSupport.unpark(node.thread);
- return true;
- }
上面就是唤醒一个await*()线程的过程,根据前面的小节介绍的,如果要unpark线程,并使线程拿到锁,那么就需要线程节点进入AQS的队列。所以可以看到在LockSupport.unpark之前调用了enq(node)操作,将当前节点加入到AQS队列。
参考文献:
http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
http://blog.sina.com.cn/s/blog_7898b053010175qv.html
*ReentrantReadWriteLock读写锁
Java读写锁实现类是ReentrantReadWriteLock,实现高效的并发读写操作。
进入读锁的条件
1.没有其他线程获取写锁;
2.没有写请求或者有但是写请求与自身是同一线程。
进入写锁的条件
1.没有其他线程获取读锁;
2.没有其他线程获取写锁。
参考文献:
http://www.cnblogs.com/liuling/archive/2013/08/21/2013-8-21-03.html
相关推荐
`ReentrantLock`是Java并发编程中的一种重要工具,它允许线程在已经获取锁的情况下再次获得同一把锁,而不必等待解锁。这在处理递归调用或嵌套锁的场景中特别有用。`ReentrantLock`提供了一种可选的公平策略,即线程...
`ReentrantLock`是Java并发编程中的一种高级锁机制,它是`java.util.concurrent.locks`包中的类,提供了比`synchronized`关键字更丰富的功能和更细粒度的控制。相较于`synchronized`,`ReentrantLock`的主要优势在于...
2. **同步机制**:详述了Java中同步的几种方法,如`synchronized`关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition。这些机制用于防止数据竞争和确保线程安全。 3. **并发工具类**:Java并发包...
#### 一、Java并发概述 自Java诞生之初,其设计者就赋予了该语言强大的并发处理能力。Java语言内置了对线程和锁的支持,这使得开发者能够轻松地编写多线程应用程序。本文旨在帮助Java开发者深入理解并发的核心概念...
3. **锁**:Java并发库中的`java.util.concurrent.locks`包提供了更高级的锁机制,如可重入锁(`ReentrantLock`)、读写锁(`ReadWriteLock`)和条件变量(`Condition`),这些工具允许更灵活的控制并发访问。 4. **并发...
本资料“Java并发编程设计原则和模式”深入探讨了如何在Java环境中有效地进行并发处理,以充分利用系统资源并避免潜在的并发问题。 一、并发编程基础 并发是指两个或多个操作在同一时间段内执行,但并不意味着这些...
ReentrantLock的这些特性使得它在某些复杂的并发场景下更为适用,例如,当需要精确控制线程间的交互,或者需要在等待期间响应中断信号时。不过,对于简单的同步控制,使用synchronized通常更简洁。在选择使用哪种锁...
Java并发编程是Java语言中最为复杂且重要的部分之一,它涉及了多线程编程、内存模型、同步机制等多个领域。为了深入理解Java并发编程,有必要了解其核心技术点和相关实现原理,以下将详细介绍文件中提及的关键知识点...
4. **锁与并发工具类**:介绍了Java.util.concurrent包中的锁机制,如ReentrantLock、读写锁(ReentrantReadWriteLock)、Condition接口,以及Semaphore、CountDownLatch、CyclicBarrier等并发工具类的使用场景和...
Java并发编程是指在Java语言中编写多线程和多任务执行的程序,以便更高效地利用计算机的多核处理器资源。并发编程是Java高级编程技能中的重要组成部分,尤其是在需要处理大量数据、提供快速响应、实现高吞吐量和高可...
2. **同步机制**:详细解析了Java中的同步工具,如`synchronized`关键字、volatile变量、java.util.concurrent包中的Lock接口及其实现,如ReentrantLock,以及Condition等。同时,还会探讨如何正确地使用这些机制...
在Java并发编程中,理解和熟练使用同步机制是至关重要的,这包括了`ReentrantLock`和`synchronized`关键字。这两个工具都是为了确保多线程环境中的数据一致性与安全性,防止出现竞态条件和死锁等问题。 `...
2. **同步机制**:Java提供了多种同步工具,如synchronized关键字、wait()、notify()和notifyAll()方法,以及Lock接口(如ReentrantLock)和Condition接口。这些机制用于控制对共享资源的访问,防止数据不一致性。 ...
《Java并发编程实战》这本书是Java开发者深入理解并发编程的重要参考书籍。本书旨在帮助程序员解决在多线程环境中遇到的实际问题,提升系统性能并保证其稳定性。随书源码提供了丰富的示例,让读者能够动手实践,加深...
《Java并发编程》是一本深度探讨Java平台上的并发与多线程编程的权威书籍,适合对并发编程有深入了解需求的开发者阅读,特别是对于那些志在加入BAT(百度、阿里巴巴、腾讯)等顶级互联网企业的程序员来说,这本书是...
《Java 并发编程实战》是一本专注于Java并发编程领域的权威书籍,旨在帮助开发者深入理解和掌握在多线程环境中编写高效、安全且可维护的代码。这本书涵盖了Java并发编程的基础概念,高级特性以及最佳实践,是Java...
Java并发编程是Java开发中的重要领域,它涉及到多线程、同步、锁机制、线程池等关键概念,是提高程序性能和效率的关键技术。在Java中,并发编程的运用可以充分利用多核处理器的能力,实现高效的多任务处理。以下是对...
这本书涵盖了Java并发编程的核心概念和技术,旨在帮助开发者在实际项目中高效地处理高并发场景。随书附带的代码提供了丰富的示例,以便读者能够更直观地理解并实践这些理论知识。 1. **Java并发基础** - **线程与...
2. **同步机制**:Java提供了多种同步机制来确保线程安全,包括synchronized关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition等。synchronized提供了方法和块级别的锁,而volatile则保证了变量的...
Java并发工具类是Java平台提供的一系列用于处理多线程并发问题的类和接口,它们在`java.util.concurrent`包中。并发问题主要包括资源竞争、死锁、活锁以及饥饿等,合理使用并发工具可以有效地提高程序的并发性能并...