ReentrantLock


synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。 
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处理了这个中断,并且不再等待这个锁的到来,完全放弃。(如果你没有了解java的中断机制,请参考下相关资料,再回头看这篇文章,80%的人根本没有真正理解什么是java的中断,呵呵) 

这里来做个试验,首先搞一个Buffer类,它有读操作和写操作,为了不读到脏数据,写和读都需要加锁,我们先用synchronized原语来加锁,如下: 
[java]  view plain copy
  1. package cn.vicky.chapt10;  
  2.   
  3. /** 
  4.  * 
  5.  * @author Vicky.H 
  6.  */  
  7. public class Buffer {  
  8.   
  9.     private Object lock;  
  10.   
  11.     public Buffer() {  
  12.         lock = this;  
  13.     }  
  14.   
  15.     public void write() {  
  16.         synchronized (lock) {  
  17.             long startTime = System.currentTimeMillis();  
  18.             System.out.println("开始往这个buff写入数据…");  
  19.             for (;;)// 模拟要处理很长时间      
  20.             {  
  21.                 if (System.currentTimeMillis()  
  22.                         - startTime > Integer.MAX_VALUE) {  
  23.                     break;  
  24.                 }  
  25.             }  
  26.             System.out.println("终于写完了");  
  27.         }  
  28.     }  
  29.   
  30.     public void read() {  
  31.         synchronized (lock) {  
  32.             System.out.println("从这个buff读数据");  
  33.         }  
  34.     }  
  35.   
  36.     public static void main(String[] args) {  
  37.         Buffer buff = new Buffer();  
  38.   
  39.         final Writer writer = new Writer(buff);  
  40.         final Reader reader = new Reader(buff);  
  41.   
  42.         writer.start();  
  43.         reader.start();  
  44.   
  45.         new Thread(new Runnable() {  
  46.   
  47.             @Override  
  48.             public void run() {  
  49.                 long start = System.currentTimeMillis();  
  50.                 for (;;) {  
  51.                     //等5秒钟去中断读      
  52.                     if (System.currentTimeMillis()  
  53.                             - start > 5000) {  
  54.                         System.out.println("不等了,尝试中断");  
  55.                         reader.interrupt();  
  56.                         break;  
  57.                     }  
  58.   
  59.                 }  
  60.   
  61.             }  
  62.         }).start();  
  63.         // 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,  
  64.         // 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,  
  65.         // 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,  
  66.         // 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。  
  67.     }  
  68. }  
  69.   
  70. class Writer extends Thread {  
  71.   
  72.     private Buffer buff;  
  73.   
  74.     public Writer(Buffer buff) {  
  75.         this.buff = buff;  
  76.     }  
  77.   
  78.     @Override  
  79.     public void run() {  
  80.         buff.write();  
  81.     }  
  82. }  
  83.   
  84. class Reader extends Thread {  
  85.   
  86.     private Buffer buff;  
  87.   
  88.     public Reader(Buffer buff) {  
  89.         this.buff = buff;  
  90.     }  
  91.   
  92.     @Override  
  93.     public void run() {  
  94.   
  95.         buff.read();//这里估计会一直阻塞      
  96.   
  97.         System.out.println("读结束");  
  98.   
  99.     }  
  100. }  

[java]  view plain copy
  1. package cn.vicky.chapt10;  
  2.   
  3. import java.util.concurrent.locks.ReentrantLock;  
  4.   
  5. /** 
  6.  * 
  7.  * @author Vicky.H 
  8.  */  
  9. public class BufferInterruptibly {  
  10.   
  11.     private ReentrantLock lock = new ReentrantLock();  
  12.   
  13.     public void write() {  
  14.         lock.lock();  
  15.         try {  
  16.             long startTime = System.currentTimeMillis();  
  17.             System.out.println("开始往这个buff写入数据…");  
  18.             for (;;)// 模拟要处理很长时间      
  19.             {  
  20.                 if (System.currentTimeMillis()  
  21.                         - startTime > Integer.MAX_VALUE) {  
  22.                     break;  
  23.                 }  
  24.             }  
  25.             System.out.println("终于写完了");  
  26.         } finally {  
  27.             lock.unlock();  
  28.         }  
  29.     }  
  30.   
  31.     public void read() throws InterruptedException {  
  32.         lock.lockInterruptibly();// 注意这里,可以响应中断      
  33.         try {  
  34.             System.out.println("从这个buff读数据");  
  35.         } finally {  
  36.             lock.unlock();  
  37.         }  
  38.     }  
  39.   
  40.     public static void main(String args[]) {  
  41.         BufferInterruptibly buff = new BufferInterruptibly();  
  42.   
  43.         final Writer2 writer = new Writer2(buff);  
  44.         final Reader2 reader = new Reader2(buff);  
  45.   
  46.         writer.start();  
  47.         reader.start();  
  48.   
  49.         new Thread(new Runnable() {  
  50.   
  51.             @Override  
  52.             public void run() {  
  53.                 long start = System.currentTimeMillis();  
  54.                 for (;;) {  
  55.                     if (System.currentTimeMillis()  
  56.                             - start > 5000) {  
  57.                         System.out.println("不等了,尝试中断");  
  58.                         reader.interrupt();  
  59.                         break;  
  60.                     }  
  61.                 }  
  62.             }  
  63.         }).start();  
  64.   
  65.     }  
  66. }  
  67.   
  68. class Reader2 extends Thread {  
  69.   
  70.     private BufferInterruptibly buff;  
  71.   
  72.     public Reader2(BufferInterruptibly buff) {  
  73.         this.buff = buff;  
  74.     }  
  75.   
  76.     @Override  
  77.     public void run() {  
  78.   
  79.         try {  
  80.             buff.read();//可以收到中断的异常,从而有效退出      
  81.         } catch (InterruptedException e) {  
  82.             System.out.println("我不读了");  
  83.         }  
  84.   
  85.         System.out.println("读结束");  
  86.   
  87.     }  
  88. }  
  89.   
  90. class Writer2 extends Thread {  
  91.   
  92.     private BufferInterruptibly buff;  
  93.   
  94.     public Writer2(BufferInterruptibly buff) {  
  95.         this.buff = buff;  
  96.     }  
  97.   
  98.     @Override  
  99.     public void run() {  
  100.         buff.write();  
  101.     }  
  102.       
  103. }  


2个程序,运行结果:

run:
开始往这个buff写入数据…
不等了,尝试中断 

 

 

run:
开始往这个buff写入数据…
不等了,尝试中断
我不读了
读结束

 

 

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()
   公平模式下:直接走AQS的acquire函数,此函数的逻辑走一次tryAcquire,如果成功
   线程拜托同步器的控制,否则加入NODE链表,进入acquireQueued的tryAcquire,休眠,被唤醒的轮回
   不公平模式下和公平模式下逻辑大体上是一样的,不同点有两个:
   a. 在执行tryAcquire之前的操作,不公平模式会直接compareAndSetState(0, 1)原子性的设置AQS的资源
   0表示目前没有线程占据资源,则直接抢占资源,不管AQS的NODE链表的FIFO原则
   b. tryAcquire的原理不一样,不公平模式的tryAcquire只看compareAndSetState(0, 1)能否成功
   而公平模式还会加一个条件就是此线程对于的NODE是不是NODE链表的第一个
   c. 由于tryAcquire的实现不一样,而公平模式和不公平模式在lock期间走的逻辑是一样的(AQS的acquireQueued的逻辑)
   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逻辑,如果再减1后AQS资源变成0则表示调用线程测得放弃了此锁,返回给release的值的TRUE,
   release会唤醒下一个线程
-----------------------------------------------------------------------
整体来看ReentrantLock互斥锁的实现大致是
1. 自己实现AQS的tryAcquire和tryRelease逻辑,tryAcquire表示尝试去获取锁,tryRelease表示尝试去释放锁
2. ReentrantLock对lock(),trylock(),trylock(time),unlock()的实现都是使用AQS的框架,然后AQS的框架又返回调用
ReentrantLock实现的tryAcquire和tryRelease来对线程是否获取锁和释放锁成功做出依据判断

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值