首页 > java基础 > 深入研究ReentrantLock(重入锁)之引出话题篇

深入研究ReentrantLock(重入锁)之引出话题篇

2009年5月30日

一直以来都想好好研究下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原语来加锁,如下:

  1. public class Buffer {
  2.  
  3.     private Object lock;
  4.  
  5.     public Buffer() {
  6.         lock = this;
  7.     }
  8.  
  9.     public void write() {
  10.         synchronized (lock) {
  11.             long startTime = System.currentTimeMillis();
  12.             System.out.println("开始往这个buff写入数据…");
  13.             for (;;)// 模拟要处理很长时间
  14.             {
  15.                 if (System.currentTimeMillis()
  16.                         - startTime > Integer.MAX_VALUE)
  17.                     break;
  18.             }
  19.             System.out.println("终于写完了");
  20.         }
  21.     }
  22.  
  23.     public void read() {
  24.         synchronized (lock) {
  25.             System.out.println("从这个buff读数据");
  26.         }
  27.     }
  28. }

接着,我们来定义2个线程,一个线程去写,一个线程去读。

  1. public class Writer extends Thread {
  2.  
  3.     private Buffer buff;
  4.  
  5.     public Writer(Buffer buff) {
  6.         this.buff = buff;
  7.     }
  8.  
  9.     @Override
  10.     public void run() {
  11.         buff.write();
  12.     }
  13.  
  14. }
  15.  
  16. public class Reader extends Thread {
  17.  
  18.     private Buffer buff;
  19.  
  20.     public Reader(Buffer buff) {
  21.         this.buff = buff;
  22.     }
  23.  
  24.     @Override
  25.     public void run() {
  26.  
  27.         buff.read();//这里估计会一直阻塞
  28.  
  29.         System.out.println("读结束");
  30.  
  31.     }
  32.  
  33. }

好了,写一个Main来试验下,我们有意先去“写”,然后让“读”等待,“写”的时间是无穷的,就看“读”能不能放弃了。

  1. public class Test {
  2.     public static void main(String[] args) {
  3.         Buffer buff = new Buffer();
  4.  
  5.         final Writer writer = new Writer(buff);
  6.         final Reader reader = new Reader(buff);
  7.  
  8.         writer.start();
  9.         reader.start();
  10.  
  11.         new Thread(new Runnable() {
  12.  
  13.             @Override
  14.             public void run() {
  15.                 long start = System.currentTimeMillis();
  16.                 for (;;) {
  17.                     //等5秒钟去中断读
  18.                     if (System.currentTimeMillis()
  19.                             - start > 5000) {
  20.                         System.out.println("不等了,尝试中断");
  21.                         reader.interrupt();
  22.                         break;
  23.                     }
  24.  
  25.                 }
  26.  
  27.             }
  28.         }).start();
  29.  
  30.     }
  31. }

我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。

  1. import java.util.concurrent.locks.ReentrantLock;
  2.  
  3. public class BufferInterruptibly {
  4.  
  5.     private ReentrantLock lock = new ReentrantLock();
  6.  
  7.     public void write() {
  8.         lock.lock();
  9.         try {
  10.             long startTime = System.currentTimeMillis();
  11.             System.out.println("开始往这个buff写入数据…");
  12.             for (;;)// 模拟要处理很长时间
  13.             {
  14.                 if (System.currentTimeMillis()
  15.                         - startTime > Integer.MAX_VALUE)
  16.                     break;
  17.             }
  18.             System.out.println("终于写完了");
  19.         } finally {
  20.             lock.unlock();
  21.         }
  22.     }
  23.  
  24.     public void read() throws InterruptedException {
  25.         lock.lockInterruptibly();// 注意这里,可以响应中断
  26.         try {
  27.             System.out.println("从这个buff读数据");
  28.         } finally {
  29.             lock.unlock();
  30.         }
  31.     }
  32.  
  33. }

当然,要对reader和writer做响应的修改

  1. public class Reader extends Thread {
  2.  
  3.     private BufferInterruptibly buff;
  4.  
  5.     public Reader(BufferInterruptibly buff) {
  6.         this.buff = buff;
  7.     }
  8.  
  9.     @Override
  10.     public void run() {
  11.  
  12.         try {
  13.             buff.read();//可以收到中断的异常,从而有效退出
  14.         } catch (InterruptedException e) {
  15.             System.out.println("我不读了");
  16.         }
  17.        
  18.         System.out.println("读结束");
  19.  
  20.     }
  21.  
  22. }
  23.  
  24. /**
  25. * Writer倒不用怎么改动
  26. */
  27. public class Writer extends Thread {
  28.  
  29.     private BufferInterruptibly buff;
  30.  
  31.     public Writer(BufferInterruptibly buff) {
  32.         this.buff = buff;
  33.     }
  34.  
  35.     @Override
  36.     public void run() {
  37.         buff.write();
  38.     }
  39.  
  40. }
  41.  
  42. public class Test {
  43.     public static void main(String[] args) {
  44.         BufferInterruptibly buff = new BufferInterruptibly();
  45.  
  46.         final Writer writer = new Writer(buff);
  47.         final Reader reader = new Reader(buff);
  48.  
  49.         writer.start();
  50.         reader.start();
  51.  
  52.         new Thread(new Runnable() {
  53.  
  54.             @Override
  55.             public void run() {
  56.                 long start = System.currentTimeMillis();
  57.                 for (;;) {
  58.                     if (System.currentTimeMillis()
  59.                             - start > 5000) {
  60.                         System.out.println("不等了,尝试中断");
  61.                         reader.interrupt();
  62.                         break;
  63.                     }
  64.  
  65.                 }
  66.  
  67.             }
  68.         }).start();
  69.  
  70.     }
  71. }

这次“读”线程接收到了lock.lockInterruptibly()中断,并且有效处理了这个“异常”。好奇的读者,肯定要探个究竟,为什么ReentrantLock能做到这点,接下来,我们去迷宫探险吧……

java基础 , , ,

  1. xlongbuilder
    2009年6月30日14:17 | #1

    不错 受益匪浅啊,希望能继续
    我转到 http://www.javaeye.com/topic/417539
    希望有空参与讨论

  1. 目前还没有任何 trackbacks 和 pingbacks.