Java锁ReentrantLock

        java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。 

        Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。 

一、锁的概念

        1.可重入锁

        可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。也就是说如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。

        如果锁具备可重入性,则称作为可重入锁。像 synchronized和 ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个 synchronized方法时,比如说 methodA,而在 methodA中会调用另外一个synchronized方法 methodB,此时线程不必重新去申请锁,而是可以直接执行方法 methodB。

        如以下情况:

Java代码   收藏代码
  1. public synchronized void methodA() {  
  2.     // 調用相同监视器对象中的其他 synchronized方法  
  3.     this.methodB();  
  4. }  
  5.   
  6. public synchronized void methodB() {  
  7.     // 其他代码  
  8. }  

        因为进入 methodA时已经获得了该监视器对象持有的锁,当从 methodA跳转到 methodB时就不必再去获取锁了。

        所以使用以上代码修改后的示例为:

Java代码   收藏代码
  1. public class LockTest implements Runnable {  
  2.   
  3.     public synchronized void methodA() {  
  4.         System.out.println("methodA:" + Thread.currentThread().getId());  
  5.         // 调用同线程内另一个 synchronized方法  
  6.         methodB();  
  7.     }  
  8.   
  9.     public synchronized void methodB() {  
  10.         System.out.println("methodB:" + Thread.currentThread().getId());  
  11.     }  
  12.   
  13.     public void run() {  
  14.         methodA();  
  15.     }  
  16.   
  17.     public static void main(String[] args) {  
  18.         LockTest lt = new LockTest();  
  19.         new Thread(lt).start();  
  20.         new Thread(lt).start();  
  21.     }  
  22. }  
  23. //结果:  
  24. methodA:9  
  25. methodB:9  
  26. methodA:10  
  27. methodB:10  

        假如 synchronized不具备可重入性,此时 methodA线程就需要重新申请锁。但是这就会造成一个问题,因为 methodA线程已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会造成 methodA线程一直等待永远不会获取到的锁。

        所以可重入锁最大的作用是避免死锁

2.可中断锁

  顾名思义,就是在某些条件下可以相应中断的锁。

  在Java中,synchronized就不是可中断锁,而 Lock是可中断锁。

  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  使用reentrantlock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断,实现了锁的中断。

        以下是中断锁的一个示例应用:

Java代码   收藏代码
  1. import java.util.concurrent.locks.Lock;  
  2. import java.util.concurrent.locks.ReentrantLock;  
  3.   
  4. public class LockTest implements Runnable {  
  5.     private Lock lock = new ReentrantLock();  
  6.   
  7.     public void methodA() throws InterruptedException {  
  8.   
  9.         lock.lockInterruptibly(); // 如果抛出InterruptedException异常说明已经被中断,需要在外层判断处理  
  10.         try {  
  11.             System.out.println(Thread.currentThread().getName() + " 获得锁");  
  12.             long startTime = System.currentTimeMillis();  
  13.             // 等待5秒  
  14.             for (;;) {  
  15.                 if (System.currentTimeMillis() - startTime >= 5000)  
  16.                     break;  
  17.             }  
  18.         } finally {  
  19.             lock.unlock();  
  20.             System.out.println(Thread.currentThread().getName() + " 释放锁");  
  21.         }  
  22.   
  23.     }  
  24.   
  25.     public void run() {  
  26.         try {  
  27.             methodA();  
  28.         } catch (InterruptedException e) {  
  29.             System.out.println(Thread.currentThread().getName() + " 被中断");  
  30.         }  
  31.     }  
  32.   
  33.     public static void main(String[] args) {  
  34.         LockTest lt = new LockTest();  
  35.         Thread t1 = new Thread(lt);  
  36.         Thread t2 = new Thread(lt);  
  37.         t1.start();  
  38.         t2.start();  
  39.         t2.interrupt();  
  40.     }  
  41. }  
  42. //结果:  
  43. Thread-0 获得锁  
  44. Thread-1 被中断  
  45. Thread-0 释放锁  

        当调用 lockInterruptibly()方法中断锁的获取时,会抛出 InterruptedException异常。这里不应该使用catch捕获异常,否则将继续执行 lockInterruptibly()方法之后的代码,从而报未获取锁的错误。应向外层抛出该异常以证明获取锁操作已经被中断,从而进行其他处理。

  3.公平锁

        公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

        而非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

        在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

        而对于 ReentrantLock和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是在初始化时可以设置为公平锁。

Java代码   收藏代码
  1. Lock lock=new ReentrantLock(true);  
        如果参数为 true表示为公平锁,为 fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

        在 ReentrantLock中定义了2个静态内部类,一个是 NotFairSync,一个是 FairSync,分别用来实现非公平锁和公平锁。ReentrantLock中还有很多与这两种锁相关的方法,在下面的章节中会逐一介绍。

 

        4.读写锁

        读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

        正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

        ReadWriteLock就是读写锁接口,ReentrantReadWriteLock是这个接口的实现。

        可以通过 readLock()获取读锁,通过 writeLock()获取写锁。

二、ReentrantLock

        1.简介

        java.util.concurrent.lock 中的 Lock 框架是锁的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上)

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

        可重入锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

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

2.构造方法

        ReentrantLock类的构造方法接受一个可选的公平(fair)参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。 

        以下是 ReentrantLock的两种构造方法:

Java代码   收藏代码
  1. /** 
  2.  * 创建 ReentrantLock实例,相当于使用 new ReentrantLock(false); 
  3.  */  
  4. public ReentrantLock() {  
  5.     sync = new NonfairSync();  
  6. }  
  7.   
  8. /** 
  9.  * 根据指定的公平策略创建 ReentrantLock实例 
  10.  */  
  11. public ReentrantLock(boolean fair) {  
  12.     sync = (fair) ? new FairSync() : new NonfairSync();  
  13. }  

        默认构造方法相当于构建了一个非公平策略的 ReentrantLock实例。

 

        3.ReentrantLock使用

        1)lock()方法

         之前已经有相关实例展现了 lock()方法的使用,使用 lock方法值得注意的是需要在finally块中主动释放锁,否则其他线程将阻塞。

Java代码   收藏代码
  1. public class LockThread {  
  2.     Lock lock = new ReentrantLock();  
  3.   
  4.     public void lock() {  
  5.         // 获取锁  
  6.         try {  
  7.             lock.lock();  
  8.             System.out.println(Thread.currentThread().getName() + " get the lock");  
  9.         } finally {  
  10.             // 释放锁  
  11.             lock.unlock();  
  12.             System.out.println(Thread.currentThread().getName() + " release the lock");  
  13.         }  
  14.     }  
  15.   
  16.     public static void main(String[] args) {  
  17.         final LockThread lt = new LockThread();  
  18.         new Thread(new Runnable() {  
  19.             public void run() {  
  20.                 lt.lock();  
  21.             }  
  22.         }).start();  
  23.         new Thread(new Runnable() {  
  24.             public void run() {  
  25.                 lt.lock();  
  26.             }  
  27.         }).start();  
  28.     }  
  29. }  
  30. //结果:  
  31. Thread-0 get the lock  
  32. Thread-0 release the lock  
  33. Thread-1 get the lock  
  34. Thread-1 release the lock  

        2)unlock()方法

        unlock方法需要配合 lock()方法使用,unlock方法需要在 catch或 finally块中声明。当未获得锁时,使用unlock方法会抛出 IllegalMonitorStateException异常。

        注释以上代码中 lock方法,将产生以下结果:

Java代码   收藏代码
  1. Exception in thread "Thread-0" java.lang.IllegalMonitorStateException  
  2.     at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:127)  
  3.     at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1175)  
  4.     at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:431)  
  5.     at LockThread.lock(LockThread.java:14)  
  6.     at LockThread$1.run(LockThread.java:23)  
  7.     at java.lang.Thread.run(Thread.java:619)  
  8. Exception in thread "Thread-1" java.lang.IllegalMonitorStateException  
  9.     at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:127)  
  10.     at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1175)  
  11.     at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:431)  
  12.     at LockThread.lock(LockThread.java:14)  
  13.     at LockThread$2.run(LockThread.java:28)  
  14.     at java.lang.Thread.run(Thread.java:619)  

        3)tryLock()方法

        tryLock方法仅在调用时锁未被另一个线程保持的情况下,才获取该锁。

        如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。即使已将此锁设置为使用公平排序策略,但是调用 tryLock() 仍将 立即获取锁(如果有可用的),而不管其他线程当前是否正在等待该锁。在某些情况下,此“闯入”行为可能很有用,即使它会打破公平性也如此。如果希望遵守此锁的公平设置,则使用 tryLock(0, TimeUnit.SECONDS) ,它几乎是等效的(也检测中断)。 

        如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true。 如果锁被另一个线程保持,则此方法将立即返回 false 值。 

        示例代码如下:

Java代码   收藏代码
  1. public class LockThread {  
  2.     Lock lock = new ReentrantLock();  
  3.   
  4.     public void lock() {  
  5.         // 尝试获取锁  
  6.         if (lock.tryLock()) {  
  7.             try {  
  8.                 System.out.println(Thread.currentThread().getName() + " get the lock");  
  9.                 while (true) {  
  10.                     //block here  
  11.                 }  
  12.             } finally {  
  13.                 // 释放锁  
  14.                 lock.unlock();  
  15.                 System.out.println(Thread.currentThread().getName() + " release the lock");  
  16.             }  
  17.         } else {  
  18.             System.out.println(Thread.currentThread().getName() + " get the lock fail");  
  19.         }  
  20.     }  
  21.   
  22.     public static void main(String[] args) {  
  23.         final LockThread lt = new LockThread();  
  24.         new Thread(new Runnable() {  
  25.             public void run() {  
  26.                 lt.lock();  
  27.             }  
  28.         }).start();  
  29.         new Thread(new Runnable() {  
  30.             public void run() {  
  31.                 lt.lock();  
  32.             }  
  33.         }).start();  
  34.     }  
  35. }  
  36. //结果:  
  37. Thread-0 get the lock  
  38. Thread-1 get the lock fail  

        其中利用 while循环产生阻塞,导致 Thread-0线程无法释放锁。当 Thread-1利用 tryLock方法尝试获取锁时,发现锁暂时无法被获取,tryLock方法返回 false,获取锁失败。

        4)tryLock(long timeout, TimeUnit unit)方法

        如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。 

        如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。如果为了使用公平的排序策略,已经设置此锁,并且其他线程都在等待该锁,则不会 获取一个可用的锁。这与 tryLock() 方法相反。如果想使用一个允许闯入公平锁的定时 tryLock,那么可以将定时形式和不定时形式组合在一起: 

 

Java代码   收藏代码
  1. if (lock.tryLock() || lock.tryLock(timeout, unit) ) {  
  2.  ...   
  3. }  

 

        如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于 0,则此方法根本不会等待。 

        将tryLock部分示例中的lock方法代码修改为:

Java代码   收藏代码
  1. public void lock() {  
  2.     try {  
  3.         // 尝试10秒内获取锁  
  4.         if (lock.tryLock() || lock.tryLock(10L, TimeUnit.SECONDS)) {  
  5.             try {  
  6.                 System.out.println(Thread.currentThread().getName() + " get the lock");  
  7.             } finally {  
  8.                 long startTime = System.currentTimeMillis();  
  9.                 for (;;) {  
  10.                     if (System.currentTimeMillis() - startTime >= 5000) {  
  11.                         // 释放锁  
  12.                         System.out.println(Thread.currentThread().getName() + " release the lock");  
  13.                         lock.unlock();  
  14.                         break;  
  15.                     }  
  16.                 }  
  17.             }  
  18.         } else {  
  19.             System.out.println(Thread.currentThread().getName() + " get the lock fail");  
  20.         }  
  21.     } catch (InterruptedException e) {  
  22.         e.printStackTrace();  
  23.     }  
  24. }  
  25. //结果:  
  26. Thread-0 get the lock  
  27. Thread-0 release the lock  
  28. Thread-1 get the lock  
  29. Thread-1 release the lock  

        结果打印正确。如果将阻塞时间修改的比 tryLock方法时间要长,则结果为:

Java代码   收藏代码
  1. Thread-0 get the lock  
  2. Thread-1 get the lock fail  
  3. Thread-0 release the lock  

        5)lockInterruptibly()方法

        如果当前线程未被中断,则获取锁。 

        如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。 

        如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回。 

        如果锁被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:

        • 锁由当前线程获得;或者 

        • 其他某个线程中断当前线程。

        如果当前线程获得该锁,则将锁保持计数设置为 1。 

        如果当前线程: 

        • 在进入此方法时已经设置了该线程的中断状态;或者 

        • 在等待获取锁的同时被中断。 

        则抛出 InterruptedException,并且清除当前线程的已中断状态。 

        在此实现中,因为此方法是一个显式中断点,所以要优先考虑响应中断,而不是响应锁的普通获取或重入获取。 

        lockInterruptibly方法的示例在本文刚开始时已经展现,这里就不再复述了。

        6)getHoldCount()方法

        查询当前线程保持此锁的次数。 

        对于与解除锁操作不匹配的每个锁操作,线程都会保持一个锁。 

        保持计数信息通常只用于测试和调试。例如,如果不应该使用已经保持的锁进入代码的某一部分,则可以声明如下: 

Java代码   收藏代码
  1. ReentrantLock lock = new ReentrantLock();  
  2. assert lock.getHoldCount() == 0;  
  3. lock.lock();  
  4. try {  
  5.     // ...  
  6. finally {  
  7.     lock.unlock();  
  8. }  

        其中 assert关键字用法如下:

        (1)assert <boolean表达式>

        如果<boolean表达式>为true,则程序继续执行。

        如果为false,则程序抛出AssertionError,并终止执行。

        (2)assert <boolean表达式> : <错误信息表达式>

        如果<boolean表达式>为true,则程序继续执行。

        如果为false,则程序抛出java.lang.AssertionError,并输入<错误信息表达式>。


原文地址:http://286.iteye.com/blog/2296191


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值