java concurrent系列3---ReentrantLock

来源:互联网 发布:ghost软件哪个好 编辑:程序博客网 时间:2024/05/17 04:24

ReentrantLock 一个可重入的互斥锁,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

此类的构造方法接受一个可选的公平参数。当设置为 true时,在多个线程的争用下,这些锁定倾向于将访问权授予等待时间最长的线程。否则此锁定将无法保证任何特定访问顺序。

package concurrent;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.ReentrantLock;public class MyReentrantLock extends Thread{TestReentrantLock lock;private int id;public MyReentrantLock(int i,TestReentrantLock test){    this.id=i;    this.lock=test;}public void run(){    lock.print(id);}public static void main(String args[]){    ExecutorService service=Executors.newCachedThreadPool();    TestReentrantLock lock=new TestReentrantLock();    for(int i=0;i<10;i++){     service.submit(new MyReentrantLock(i,lock));    }    service.shutdown();}}class TestReentrantLock{private ReentrantLock lock=new ReentrantLock();public void print(int str){    try{     lock.lock();     System.out.println(str+"获得");     Thread.sleep((int)(Math.random()*1000));    }    catch(Exception e){     e.printStackTrace();    }    finally{     System.out.println(str+"释放");     lock.unlock();    }}}


此处的ReentrantLock 与synchronized用法相同。

 

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能做到这点,接下来,我们去迷宫探险吧……