论坛首页 Java企业应用论坛

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

浏览 2335 次
该帖已经被评为隐藏帖
作者 正文
   发表时间:2009-06-30   最后修改:2009-06-30
转自:http://www.crabone.com/index.php/2009/05/30/291.html

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

public class Buffer { 
  
    private Object lock; 
  
    public Buffer() { 
        lock = this; 
    } 
  
    public void write() { 
        synchronized (lock) { 
            long startTime = System.currentTimeMillis(); 
            System.out.println("开始往这个buff写入数据…"); 
            for (;;)// 模拟要处理很长时间 
            { 
                if (System.currentTimeMillis() 
                        - startTime > Integer.MAX_VALUE) 
                    break; 
            } 
            System.out.println("终于写完了"); 
        } 
    } 
  
    public void read() { 
        synchronized (lock) { 
            System.out.println("从这个buff读数据"); 
        } 
    } 
} 

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

public class Writer extends Thread { 
  
    private Buffer buff; 
  
    public Writer(Buffer buff) { 
        this.buff = buff; 
    } 
  
    @Override 
    public void run() { 
        buff.write(); 
    } 
  
} 
  
public class Reader extends Thread { 
  
    private Buffer buff; 
  
    public Reader(Buffer buff) { 
        this.buff = buff; 
    } 
  
    @Override 
    public void run() { 
  
        buff.read();//这里估计会一直阻塞 
  
        System.out.println("读结束"); 
  
    } 
  
} 

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

public class Test { 
    public static void main(String[] args) { 
        Buffer buff = new Buffer(); 
  
        final Writer writer = new Writer(buff); 
        final Reader reader = new Reader(buff); 
  
        writer.start(); 
        reader.start(); 
  
        new Thread(new Runnable() { 
  
            @Override 
            public void run() { 
                long start = System.currentTimeMillis(); 
                for (;;) { 
                    //等5秒钟去中断读 
                    if (System.currentTimeMillis() 
                            - start > 5000) { 
                        System.out.println("不等了,尝试中断"); 
                        reader.interrupt(); 
                        break; 
                    } 
  
                } 
  
            } 
        }).start(); 
  
    } 
} 

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

import java.util.concurrent.locks.ReentrantLock; 
  
public class BufferInterruptibly { 
  
    private ReentrantLock lock = new ReentrantLock(); 
  
    public void write() { 
        lock.lock(); 
        try { 
            long startTime = System.currentTimeMillis(); 
            System.out.println("开始往这个buff写入数据…"); 
            for (;;)// 模拟要处理很长时间 
            { 
                if (System.currentTimeMillis() 
                        - startTime > Integer.MAX_VALUE) 
                    break; 
            } 
            System.out.println("终于写完了"); 
        } finally { 
            lock.unlock(); 
        } 
    } 
  
    public void read() throws InterruptedException { 
        lock.lockInterruptibly();// 注意这里,可以响应中断 
        try { 
            System.out.println("从这个buff读数据"); 
        } finally { 
            lock.unlock(); 
        } 
    } 
  
} 

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

public class Reader extends Thread { 
  
    private BufferInterruptibly buff; 
  
    public Reader(BufferInterruptibly buff) { 
        this.buff = buff; 
    } 
  
    @Override 
    public void run() { 
  
        try { 
            buff.read();//可以收到中断的异常,从而有效退出 
        } catch (InterruptedException e) { 
            System.out.println("我不读了"); 
        } 
        
        System.out.println("读结束"); 
  
    } 
  
} 
  
/** 
* Writer倒不用怎么改动 
*/ 
public class Writer extends Thread { 
  
    private BufferInterruptibly buff; 
  
    public Writer(BufferInterruptibly buff) { 
        this.buff = buff; 
    } 
  
    @Override 
    public void run() { 
        buff.write(); 
    } 
  
} 
  
public class Test { 
    public static void main(String[] args) { 
        BufferInterruptibly buff = new BufferInterruptibly(); 
  
        final Writer writer = new Writer(buff); 
        final Reader reader = new Reader(buff); 
  
        writer.start(); 
        reader.start(); 
  
        new Thread(new Runnable() { 
  
            @Override 
            public void run() { 
                long start = System.currentTimeMillis(); 
                for (;;) { 
                    if (System.currentTimeMillis() 
                            - start > 5000) { 
                        System.out.println("不等了,尝试中断"); 
                        reader.interrupt(); 
                        break; 
                    } 
  
                } 
  
            } 
        }).start(); 
  
    } 
} 
这次“读”线程接收到了lock.lockInterruptibly()中断,并且有效处理了这个“异常”。好奇的读者,肯定要探个究竟,为什么ReentrantLock能做到这点,接下来,我们去迷宫探险吧……
   发表时间:2009-12-25  
终于找到一篇能看懂的文章了。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics