`

ReentrantReadWriteLock/ReentrantLock 重入锁

阅读更多

转自:http://blog.csdn.net/vking_wang/article/details/9952063 (【Java线程】锁机制:synchronized、Lock、Condition)

 

.ReentrantReadWriteLock(读写锁)的使用

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

读写锁:分为读锁和写锁,多个读锁不互斥(共享读锁),读锁与写锁互斥(互斥写锁),这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁

线程进入读锁的前提条件:

    没有其他线程的写锁,

    没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

 

线程进入写锁的前提条件:

    没有其他线程的读锁

    没有其他线程的写锁

ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系

然后就是总结这个锁机制的特性了: 

     (a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。 

     (b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a)

     (c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。 

     (d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 

     (e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。  

 

---------- 以上摘抄自读写锁的使用http://www.cnblogs.com/liuling/p/2013-8-21-03.html

 

ReentrantReadWriteLock synchronized 的区别

 

1.拆分读写锁场景. 提高并发量:

ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。 

2.不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 

3.写锁支持Condition重入.

 

使用ReentrantReadWriteLock 

 

1,放在成员变量的位置.

2,如果声明为static 则表示是类锁.对实例对象共享.

   如果没有声明为static 则表示是对象锁. 对单列对象共享.对多个new出来的对象不共享.

3,使用时声明为final. 表示不允许修改引用指向.

4,一把对象锁在多个同步代码中的使用如下.



 

 

 

. ReentrantLock(重入锁)的使用

synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,

特别是遇到下面几种种需求的时候。 

1.某个线程在等待一个锁的控制权的这段时间需要中断 

2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程 

3.具有公平锁功能,每个到来的线程都将排队等候 

 

先说第一种情况

 

ReentrantLock的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。

这个时候ReentrantLock就提供了2种机制,

第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);

第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃。

 

ReentrantLock是一个互斥的同步器,其实现了接口Lock,里面的功能函数主要有:

1. ‍lock() -- 阻塞模式获取资源

2. ‍lockInterruptibly() -- 可中断模式获取资源

3. ‍tryLock() -- 尝试获取资源

4. tryLock(time) -- 在一段时间内尝试获取资源

5. ‍unlock() -- 释放资源

 

ReentrantLock实现Lock有两种模式即公平模式和不公平模式

Concurrent包下的同步器都是基于AQS框架,在ReentrantLock里面会看到这样三个类

-----------------------------------------------------------------------

static abstract class Sync extends AbstractQueuedSynchronizer {

    abstract void lock();

    final boolean nonfairTryAcquire(int acquires) { ... }

    protected final boolean tryRelease(int releases) { ... }

}

 

-----------------------------------------------------------------------

final static class NonfairSync extends Sync {

    protected final boolean tryAcquire(int acquires) { ... }

    final void lock() { ... }

}

-----------------------------------------------------------------------

final static class FairSync extends Sync {

    final void lock() { ... }

    protected final boolean tryAcquire(int acquires) { ... }

}

-----------------------------------------------------------------------

再回归到ReentrantLock对Lock的实现上

0. ‍ReentrantLock实例化

   ReentrantLock有个属性sync,实际上对Lock接口的实现都是包装了一下这个sync的实现

   如果是公平模式则创建一个FairSync对象,否则创建一个NonfairSync对象,默认是不公平模式

1. lock() 调用sync.lock()

   公平模式下:直接走AQSacquire函数,此函数的逻辑走一次tryAcquire,如果成功

   线程拜托同步器的控制,否则加入NODE链表,进入acquireQueuedtryAcquire,休眠,被唤醒的轮回

   不公平模式下和公平模式下逻辑大体上是一样的,不同点有两个:

   a. 在执行tryAcquire之前的操作,不公平模式会直接compareAndSetState(0, 1)原子性的设置AQS的资源

   0表示目前没有线程占据资源,则直接抢占资源,不管AQSNODE链表的FIFO原则

   b. tryAcquire的原理不一样,不公平模式的tryAcquire只看compareAndSetState(0, 1)能否成功

   而公平模式还会加一个条件就是此线程对于的NODE是不是NODE链表的第一个

   c. 由于tryAcquire的实现不一样,而公平模式和不公平模式在lock期间走的逻辑是一样的(AQSacquireQueued的逻辑)

   d. 对于一个线程在获取到资源后再调用lock会导致AQS的资源做累加操作,同理线程要彻底的释放资源就必须同样

   次数的调用unlock来做对应的累减操作,因为对应ReentrantLock来说tryAcquire成功一个必须的条件就是compareAndSetState(0, 1)

   e. 由于acquireQueued过程中屏蔽了线程中断,只是在线程拜托同步器控制后,如果记录线程在此期间被中断过则标记线程的

   中断状态

2. ‍lockInterruptibly() 调用sync.acquireInterruptibly(1),上一篇文章讲过AQS的核心函数,这个过程和acquireQueued

   是一样的,只不过在阻塞期间如果被标记中断则线程在park期间被唤醒,然后直接退出那个轮回,抛出中断异常

   由于公平模式和不公平模式下对tryAcquire的实现不一样导致lockInterruptibly逻辑也是不一样

3. tryLock() 函数只是尝试性的去获取一下锁,跟tryAcquire一样,这两种模式下走的代码一样都是公平模式下的代码

4. tryLock(time) 调用sync.tryAcquireNanos(time),上一篇文章讲过AQS的核心函数,这个过程和acquireQueued一样,

   a. 在阻塞前会先计算阻塞的时间,进入休眠

   b. 如果被中断则会判断时间是否到了

      1. 如果没到则且被其他线程设置了中断标志,退出那个轮回,抛出中断异常,如果没有被设置中断标记则是前一个线程

      释放了资源再唤醒了它,其继续走那个轮回,轮回中,如果tryAcquire成功则摆脱了同步器的控制,否则回到a

      2. 如果时间到了则退出轮回,获取资源失败

5. ‍unlock() 调用sync.release(1),上一篇文章讲过AQS的核心函数,release函数会调用Sync实现的tryRelease函数来判断

   释放资源是否成功,即Sync.tryRelease函数,其逻辑过程是

   a. 首先判断目前占据资源的线程是不是调用者,如果不是会抛出异常IllegalMonitorStateException

   b. 如果是则进行AQS资源的减1逻辑,如果再减1AQS资源变成0则表示调用线程测得放弃了此锁,返回给release的值的TRUE

   release会唤醒下一个线程

-----------------------------------------------------------------------

整体来看ReentrantLock互斥锁的实现大致是

1. 自己实现AQS的tryAcquire和tryRelease逻辑,tryAcquire表示尝试去获取锁,tryRelease表示尝试去释放锁

2. ReentrantLock对lock(),trylock(),trylock(time),unlock()的实现都是使用AQS的框架,然后AQS的框架又返回调用

ReentrantLock实现的tryAcquire和tryRelease来对线程是否获取锁和释放锁成功做出依据判断

 

---------以上摘抄自重入锁的使用 http://blog.csdn.net/eclipser1987/article/details/7301828

 

 

第二种情况: 线程间通信Condition

 

Condition可以替代传统的线程间通信,await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()

——为什么方法名不直接叫wait()/notify()/nofityAll()?因为Object的这几个方法是final的,不可重写!

 

传统线程的通信方式,Condition都可以实现。

注意,Condition是被绑定到Lock上的,要创建一个LockCondition必须用newCondition()方法。

 

Condition的强大之处在于它可以为多个线程间建立不同的Condition

JDK文档中的一个例子:

假定有一个绑定的缓冲区,它支持 put  take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。

我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。

可以使用两个Condition 实例来做到这一点。

 

——其实就是java.util.concurrent.ArrayBlockingQueue的功能

  1. class BoundedBuffer {  
  1.   final Lock lock = new ReentrantLock();          //锁对象  
  2.   final Condition notFull  = lock.newCondition(); //写线程锁  
  3.   final Condition notEmpty = lock.newCondition(); //读线程锁  
  4.   
  5.   final Object[] items = new Object[100];//缓存队列  
  6.   int putptr;  //写索引  
  7.   int takeptr; //读索引  
  8.   int count;   //队列中数据数目  
  9.   
  10.   //  
  11.   public void put(Object x) throws InterruptedException {  
  12.     lock.lock(); //锁定  
  13.     try {  
  14.       // 如果队列满,则阻塞<写线程>  
  15.       while (count == items.length) {  
  16.         notFull.await();   
  17.       }  
  18.       // 写入队列,并更新写索引  
  19.       items[putptr] = x;   
  20.       if (++putptr == items.length) putptr = 0;   
  21.       ++count;  
  22.   
  23.       // 唤醒<读线程>  
  24.       notEmpty.signal();   
  25.     } finally {   
  26.       lock.unlock();//解除锁定   
  27.     }   
  28.   }  
  29.   
  30.   //   
  31.   public Object take() throws InterruptedException {   
  32.     lock.lock(); //锁定   
  33.     try {  
  34.       // 如果队列空,则阻塞<读线程>  
  35.       while (count == 0) {  
  36.          notEmpty.await();  
  37.       }  
  38.   
  39.       //读取队列,并更新读索引  
  40.       Object x = items[takeptr];   
  41.       if (++takeptr == items.length) takeptr = 0;  
  42.       --count;  
  43.   
  44.       // 唤醒<写线程>  
  45.       notFull.signal();   
  46.       return x;   
  47.     } finally {   
  48.       lock.unlock();//解除锁定   
  49.     }   
  50.   }   

 

优点:

假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。

 

那么假设只有一个Condition会有什么效果呢?缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

 

 

----- 以上摘抄自线程间通信 http://blog.csdn.net/vking_wang/article/details/9952063

  • 大小: 104.4 KB
分享到:
评论

相关推荐

    JUC知识点总结(三)ReentrantLock与ReentrantReadWriteLock源码解析

    8. Lock接口 (ReentrantLock 可重入锁) 特性 ReentantLock 继承接口 Lock 并实现了接口中定义的方法, 它是一种可重入锁, 除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁...

    各种锁汇总,乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁、行级锁等

    4. **可重入锁**:可重入锁允许一个线程获取同一资源的多次锁定,如Java的`synchronized`和`ReentrantLock`。这样可以避免死锁,当一个线程已经持有锁,尝试获取同一锁时,它能再次获得。 5. **互斥锁(Mutex)**:...

    Java 中15种锁的介绍

    Java的`ReentrantLock`和`synchronized`都是可重入锁。 - **不可重入锁** 如果线程已经在持有锁的情况下尝试再次获取,会导致死锁。例如,上述代码中的自旋锁模拟展示了不可重入锁的实现。 3. **独享锁 / 共享锁**...

    JUC最详细思维导图,一次了解读写锁,可重入锁,Cas原理,volatile 关键字原理

    在JUC中,`java.util.concurrent.locks.ReadWriteLock`接口定义了读写锁,而`ReentrantReadWriteLock`是其具体实现,它支持可重入特性,意味着持有读锁的线程可以再次获取读锁,持有写锁的线程可以获取读锁或写锁。...

    彻底理解Java中的各种锁.pdf

    Java中的ReentrantLock和synchronized都是可重入锁的实现。 4. 读写锁 读写锁是专为读多写少的场景设计的一种锁策略,它允许多个读操作并行执行,但写操作与读操作、写操作之间是互斥的。在Java中,...

    Java锁的种类以及区别

    - `ReentrantLock`就是一种典型的可重入锁。 - `Synchronized`也是可重入锁的一种实现。 #### 三、独享锁与共享锁 **1. 独享锁** 独享锁也称为排他锁,指的是在某个时间段内只允许一个线程获取锁,这意味着如果...

    Java并发编程之重入锁与读写锁

    在 Java 中,内置锁(synchronized)和 Lock(ReentrantLock)都是可重入的。synchronized 实例可以在方法或代码块上使用,以便 thread-safe,而 ReentrantLock 则需要手动加锁和解锁。例如: ```java public class...

    java中的锁

    2. ReentrantLock(可重入锁):实现了Lock接口,是Java中唯一可重入的互斥锁,与内置锁有类似的语义,但功能更强大。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks....

    读写锁.txt

    ReentrantLock//互斥锁 class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    Java分布式应用学习笔记06浅谈并发加锁机制分析

    `ReentrantLock`是一种可重入的互斥锁,支持公平和非公平两种模式。它内部有一个`Sync`类,继承自`AbstractQueuedSynchronizer`,并且有两个子类分别实现了公平和非公平锁的逻辑。 - **非公平锁** (`NonfairSync`):...

    第15讲 synchronized和ReentrantLock有什么区别呢?1

    ReentrantLock,即可重入锁,是Java 5引入的锁实现,它在功能上与synchronized相似,但提供了更多高级特性。通过调用lock()和unlock()方法,程序员可以更精确地控制锁的获取和释放。ReentrantLock的一个关键特性是可...

    详解Java多线程编程中互斥锁ReentrantLock类的用法

    1. **可重入性**:ReentrantLock允许一个线程在已经获取锁的情况下再次获取该锁,而不会发生死锁。这对于递归方法或者嵌套同步块特别有用。例如,当一个线程已经获得了锁,然后在同步代码块内部调用了另一个需要相同...

    java多线程之并发锁

    ReentrantLock 是一个可重入锁,这意味着线程可以多次获取同一个锁,直到线程释放所有的锁。ReentrantLock 的锁机制可以防止线程死锁和饥饿的发生。 FoodCenter 类和 ThreadDog、ThreadPig 类都是使用 Lock 机制来...

    ReadWriteLock接口及其实现ReentrantReadWriteLock方法

    在 Java 中,有多种锁机制,如 ReentrantLock、ReentrantReadWriteLock 等。今天,我们将详细介绍 ReadWriteLock 接口及其实现 ReentrantReadWriteLock 方法。 ReadWriteLock 接口 ReadWriteLock 接口是 Java 中的...

    java进阶提高学习教程-14锁机制.pptx

    Java 中的 ReentrantLock 和 synchronized 都是可重入锁。 读写锁 读写锁可以在同一时刻允许多个读线程访问,写线程访问时其它读线程和写线程都会被阻塞。读写锁维护一对读锁和写锁,通过读和写锁的分离,提升并发...

    Java多线程 ReentrantReadWriteLock原理及实例详解

    - **可重入性**:与ReentrantLock类似,ReentrantReadWriteLock支持锁的可重入特性,即线程可以多次获取同一类型的锁(读锁或写锁),而不会被自己阻塞。 - **公平性**:ReentrantReadWriteLock可以设置为公平模式...

    contention-profiling:ReentrantLock 和 ReentrantReadWriteLock 上的配置文件争用

    `ReentrantLock`是可重入的互斥锁,它实现了与`synchronized`关键字类似的功能,但提供了更灵活的用法。可重入意味着一个线程可以获取同一锁多次而不必担心死锁。`ReentrantLock`提供了一种公平性选项,这意味着线程...

    Java concurrency之锁_动力节点Java学院整理

    - ReentrantLock:作为独占锁,ReentrantLock提供了公平和非公平两种模式,支持锁的可重入性,具备与`synchronized`相似的功能,但提供了更多的控制选项。 理解并熟练掌握这些锁机制,可以帮助开发者更好地设计和...

    Java多线程源码笔记.pdf

    6. ReentrantLock和ReentrantReadWriteLock:ReentrantLock是可重入锁,允许多次进入同一线程,保证了线程安全。ReentrantReadWriteLock是读写锁,允许多个读线程同时访问,但写线程独占资源,提高了并发性能。 7. ...

Global site tag (gtag.js) - Google Analytics