多线程系列(九) -ReentrantLock常用方法详解

打印 上一主题 下一主题

主题 908|帖子 908|积分 2724

一、简介

在上一篇文章中,我们介绍了ReentrantLock类的一些基本用法,今天我们重点来介绍一下ReentrantLock其它的常用方法,以便对ReentrantLock类的使用有更深入的理解。
二、常用方法介绍

2.1、构造方法

ReentrantLock类有两个构造方法,核心源码内容如下:
  1. /**
  2. * 默认创建非公平锁
  3. */
  4. public ReentrantLock() {
  5.     sync = new NonfairSync();
  6. }
复制代码
  1. /**
  2. * fair为true表示是公平锁,fair为false表示是非公平锁
  3. */
  4. public ReentrantLock(boolean fair) {
  5.     sync = fair ? new FairSync() : new NonfairSync();
  6. }
复制代码
相比于synchronized同步锁,ReentrantLock有一个很大的特点,就是开发人员可以手动指定采用公平锁机制还是非公平锁机制。
公平锁:顾名思义,就是每个线程获取锁的顺序是按照线程排队的顺序来分配的,最前面的线程总是最先获取到锁。

  • 优点:所有的线程都有机会得到资源
  • 缺点:公平锁机制实现比较复杂,程序流程比较多,执行速度比较慢
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,任何线程在某时刻都有可能直接获取并拥有锁,之前介绍的synchronized其实就是一种非公平锁

  • 优点:公平锁机制实现相对比较简单,程序流程比较少,执行速度比较快
  • 缺点:有可能某些线程一直拿不到锁,导致饿死
ReentrantLock默认的构造方法是非公平锁,如果想要构造公平锁机制,只需要传入true就可以了。
示例代码如下:
  1. public static void main(String[] args) {
  2.     // 创建公平锁实现机制
  3.     Lock lock = new ReentrantLock(true);
  4.     // 创建5个线程
  5.     for (int i = 0; i < 5; i++) {
  6.         new Thread(new Runnable() {
  7.             @Override
  8.             public void run() {
  9.                 System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 启动了!");
  10.                 // 尝试获取锁
  11.                 lock.lock();
  12.                 try {
  13.                     System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 获得锁!");
  14.                 } finally {
  15.                     lock.unlock();
  16.                 }
  17.             }
  18.         }).start();
  19.     }
  20. }
复制代码
运行一下程序,结果如下:
  1. ThreadName:Thread-0, 启动了!
  2. ThreadName:Thread-1, 启动了!
  3. ThreadName:Thread-0, 获得锁!
  4. ThreadName:Thread-1, 获得锁!
  5. ThreadName:Thread-2, 启动了!
  6. ThreadName:Thread-2, 获得锁!
  7. ThreadName:Thread-3, 启动了!
  8. ThreadName:Thread-3, 获得锁!
  9. ThreadName:Thread-4, 启动了!
  10. ThreadName:Thread-4, 获得锁!
复制代码
从日志上可以看到,启动顺序为0,1,2,3,4,获取锁的顺序为0,1,2,3,4,启动与获取锁的排队机制一致。
假如我们构造方法里面的把true改成false,也就是非公平锁机制,在看看运行效果,结果如下:
  1. ThreadName:Thread-1, 启动了!
  2. ThreadName:Thread-2, 启动了!
  3. ThreadName:Thread-1, 获得锁!
  4. ThreadName:Thread-0, 启动了!
  5. ThreadName:Thread-2, 获得锁!
  6. ThreadName:Thread-3, 启动了!
  7. ThreadName:Thread-3, 获得锁!
  8. ThreadName:Thread-0, 获得锁!
  9. ThreadName:Thread-4, 启动了!
  10. ThreadName:Thread-4, 获得锁!
复制代码
从日志上可以看到,启动顺序为1,2,0,3,4,获取锁的顺序为1,2,3,0,4,线程启用与获取锁的顺序不一致。
从实际的运行结果看,非公平锁要比公平锁执行速度要快一些,当线程数越多的时候,效果越明显。
2.2、核心方法

ReentrantLock类的核心方法就比较多了,如下表!
方法描述public void lock()阻塞等待获取锁;不允许Thread.interrupt中断,即使检测到Thread.isInterrupted一样会继续尝试public void lockInterruptibly()当前线程未被中断,则获取锁;允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回public boolean tryLock()尝试申请一个锁,在成功获得锁后返回true,否则,立即返回falsepublic boolean tryLock(long timeout, TimeUnit unit)在一段时间内尝试申请一个锁,在成功获得锁后返回true,否则,立即返回falsepublic void unlock()释放锁public Condition newCondition()条件实例,用于线程等待/通知模式public int getHoldCount()获取当前线程持有此锁的次数public boolean isHeldByCurrentThread()检测是否被当前线程持有public boolean isLocked()查询此锁是否由任意线程持有public final boolean isFair()如果是公平锁返回true,否则返回falsepublic final boolean hasQueuedThreads()查询是否有线程正在等待public final boolean hasQueuedThread(Thread thread)查询给定线程是否正在等待获取此锁public final int getQueueLength()获取正等待获取此锁的线程数public boolean hasWaiters(Condition condition)是否存在正在等待并符合相关给定条件的线程public int getWaitQueueLength(Condition condition)正在等待并符合相关给定条件的线程数量虽然方法很多,但是实际上常用方法就那么几个,下面我们主要抽一些常用的方法进行介绍。
2.2.1、tryLock 方法

lock()、lockInterruptibly()、tryLock()和tryLock(long timeout, TimeUnit unit)这几个方法,目的其实是一样的,都是为了获取锁,只是针对不同的场景做了单独的处理。
lock():阻塞等待获取锁,如果没有获取到会一直阻塞,即使检测到Thread.isInterrupted一样会继续尝试;

  • lockInterruptibly():同样也是阻塞等待获取锁,稍有不同的是,允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回
  • tryLock():表示尝试申请一个锁,在成功获得锁后返回true,否则,立即返回false,不会阻塞等待获取锁
  • tryLock(long timeout, TimeUnit unit):表示在一段时间内尝试申请一个锁,在成功获得锁后返回true,否则,立即返回false
其中tryLock(long timeout, TimeUnit unit)方法的应用最广泛,因为它能防止程序发生死锁,即使在一段时间内没有获取锁,也会自动退出,不会一直阻塞。
我们可以看一个简单的例子,如下!
  1. public static void main(String[] args) {
  2.     // 创建公平锁实现机制
  3.     Lock lock = new ReentrantLock();
  4.     // 创建5个线程
  5.     for (int i = 0; i < 5; i++) {
  6.         new Thread(new Runnable() {
  7.             @Override
  8.             public void run() {
  9.                 boolean flag = false;
  10.                 try {
  11.                     // 尝试3秒内获取锁
  12.                     flag = lock.tryLock(3, TimeUnit.SECONDS);
  13.                     if(flag){
  14.                         System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 获取到锁");
  15.                         // 模拟进行5秒的业务操作
  16.                         Thread.sleep(5000);
  17.                     } else {
  18.                         System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 经过3秒钟的尝试未获取到锁,放弃尝试");
  19.                     }
  20.                 } catch (InterruptedException e) {
  21.                     e.printStackTrace();
  22.                 } finally {
  23.                     if (flag){
  24.                         System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 释放对象");
  25.                         lock.unlock();
  26.                     }
  27.                 }
  28.             }
  29.         }).start();
  30.     }
  31. }
复制代码
运行一下程序,结果如下:
  1. ThreadName:Thread-0, 获取到锁
  2. ThreadName:Thread-3, 经过3秒钟的尝试未获取到锁,放弃尝试
  3. ThreadName:Thread-1, 经过3秒钟的尝试未获取到锁,放弃尝试
  4. ThreadName:Thread-2, 经过3秒钟的尝试未获取到锁,放弃尝试
  5. ThreadName:Thread-4, 经过3秒钟的尝试未获取到锁,放弃尝试
  6. ThreadName:Thread-0, 释放对象
复制代码
可以很清晰的看到,非Thread-0线程尝试了 3 秒没有获取到锁,自动放弃;如果换成lock()方法进行获取锁,线程Thread-0如果不释放锁,其它线程会一直阻塞。
2.2.2、unlock 方法

unlock()方法也是常用方法,表示释放锁。当获取到锁之后,一定要手动释放锁,否则可能会造成其它程序执行出现问题,通常用在finally方法块里面。
  1. // 阻塞等待获取锁
  2. lock.lock();
  3. try {
  4.     // 业务操作...
  5. } finally {
  6.         // 一定要释放锁
  7.     lock.unlock();
  8. }
复制代码
2.2.3、newCondition 方法

newCondition()方法,在上文中介绍过,ReentrantLock和Condition结合,可以实现线程之间的等待/通知模型。
简单的示例,如下!
  1. public class Counter {
  2.     private final Lock lock = new ReentrantLock();
  3.     private Condition condition = lock.newCondition();
  4.     private int count;
  5.     public void await(){
  6.         // 加锁
  7.         lock.lock();
  8.         try {
  9.             // 让当前线程进入等待状态,并释放锁
  10.             condition.await();
  11.             System.out.println("await等待结束,count:" + getCount());
  12.         } catch (Exception e){
  13.             e.printStackTrace();
  14.         } finally {
  15.             // 释放锁
  16.             lock.unlock();
  17.         }
  18.     }
  19.     public void signal(){
  20.         // 加锁
  21.         lock.lock();
  22.         try {
  23.             count++;
  24.             // 唤醒某个等待线程
  25.             condition.signal();
  26.             System.out.println("signal 唤醒通知完毕");
  27.         } catch (Exception e){
  28.             e.printStackTrace();
  29.         } finally {
  30.             // 释放锁
  31.             lock.unlock();
  32.         }
  33.     }
  34.     public int getCount() {
  35.         return count;
  36.     }
  37. }
复制代码
  1. public class MyThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Counter counter = new Counter();
  4.         // 先启动执行等待的线程
  5.         new Thread(new Runnable() {
  6.             @Override
  7.             public void run() {
  8.                 counter.await();
  9.             }
  10.         }).start();
  11.         Thread.sleep(3000);
  12.         // 过3秒,再启动执行通知的线程
  13.         new Thread(new Runnable() {
  14.             @Override
  15.             public void run() {
  16.                 counter.signal();
  17.             }
  18.         }).start();
  19.     }
  20. }
复制代码
运行一下程序,结果如下:
  1. signal 唤醒通知完毕
  2. await等待结束,count:1
复制代码
2.2.4、getHoldCount 方法

getHoldCount()方法的作用是返回的是当前线程调用lock()的次数。
示例代码如下:
  1. public static void main(String[] args) {
  2.     ReentrantLock lock = new ReentrantLock();
  3.     new Thread(new Runnable() {
  4.         @Override
  5.         public void run() {
  6.             // 第一次获取锁
  7.             lock.lock();
  8.             try {
  9.                 System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" +  lock.getHoldCount());
  10.                 // 第二次获取锁
  11.                 lock.lock();
  12.                 try {
  13.                     System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" +  lock.getHoldCount());
  14.                 } finally {
  15.                     lock.unlock();
  16.                 }
  17.             } finally {
  18.                 lock.unlock();
  19.             }
  20.         }
  21.     }).start();
  22. }
复制代码
运行一下程序,结果如下:
  1. ThreadName:Thread-0, getHoldCount:1
  2. ThreadName:Thread-0, getHoldCount:2
复制代码
侧面也证明了一点,ReentrantLock和synchronized一样,锁都具有可重入特性,也就是说同一个线程多次调用同一个ReentrantLock的lock()方法,可以再次进入方法体,无需阻塞等待。
2.2.5、isLocked 方法

isHeldByCurrentThread()和isLocked()方法都是用于检测锁是否被持有。
其中isHeldByCurrentThread()方法表示此锁是否由当前线程持有;isLocked()方法表示此锁是否由任意线程持有。
我们看一个简单的示例,如下:
  1. public class Counter {
  2.     private ReentrantLock lock = new ReentrantLock();
  3.     public void methodA(){
  4.         lock.lock();
  5.         try {
  6.             System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 当前线程是否持有锁:" +  lock.isHeldByCurrentThread());
  7.             System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意线程是否持有锁:" +  lock.isLocked());
  8.             Thread.sleep(1000);
  9.         } catch (InterruptedException e) {
  10.             e.printStackTrace();
  11.         } finally {
  12.             lock.unlock();
  13.         }
  14.     }
  15.     public void methodB(){
  16.         System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 当前线程是否持有锁:" +  lock.isHeldByCurrentThread());
  17.         System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意线程是否持有锁:" +  lock.isLocked());
  18.     }
  19. }
复制代码
  1. public class MyThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Counter counter = new Counter();
  4.         new Thread(new Runnable() {
  5.             @Override
  6.             public void run() {
  7.                 counter.methodA();
  8.             }
  9.         }).start();
  10.         new Thread(new Runnable() {
  11.             @Override
  12.             public void run() {
  13.                 counter.methodB();
  14.             }
  15.         }).start();
  16.     }
  17. }
复制代码
运行一下程序,结果如下:
  1. ThreadName:Thread-0, 当前线程是否持有锁:true
  2. ThreadName:Thread-0, 任意线程是否持有锁:true
  3. ThreadName:Thread-1, 当前线程是否持有锁:false
  4. ThreadName:Thread-1, 任意线程是否持有锁:true
复制代码
从日志结果很容易理解,Thread-0线程持有锁,因此调用isHeldByCurrentThread()和isLocked()方法,返回结果都是true;Thread-1线程没有持有锁,因此isHeldByCurrentThread()方法返回false,isLocked()方法返回true
2.2.6、isFair 方法

isFair()方法用来获取此锁是否公平锁。
简单的示例,如下:
  1. ReentrantLock lock = new ReentrantLock(true);
  2. System.out.println("是否公平锁:" +  lock.isFair());
复制代码
输出结果如下:
  1. 是否公平锁:true
复制代码
ReentrantLock默认的是非公平锁,当通过构造方法显式传入true时,采用的是公平锁机制
2.2.5、hasQueuedThreads 方法

hasQueuedThreads()和hasQueuedThread()方法都用于查询是否有线程等待获取锁,稍有不同的是:hasQueuedThreads()方法表示查询是否有线程正在等待获取锁;hasQueuedThread()方法表示查询给定线程是否正在等待获取此锁。
另外还有一个getQueueLength()方法,表示获取正等待获取此锁的线程数。
我们看一个简单的示例,如下:
  1. public static void main(String[] args) throws InterruptedException {
  2.     ReentrantLock lock = new ReentrantLock();
  3.     Thread threadA = new Thread(new Runnable() {
  4.         @Override
  5.         public void run() {
  6.             lock.lock();
  7.             try {
  8.                 Thread.sleep(5000);
  9.             } catch (InterruptedException e) {
  10.                 e.printStackTrace();
  11.             } finally {
  12.                 lock.unlock();
  13.             }
  14.         }
  15.     });
  16.     threadA.start();
  17.     Thread threadB = new Thread(new Runnable() {
  18.         @Override
  19.         public void run() {
  20.             lock.lock();
  21.             try {
  22.                 Thread.sleep(5000);
  23.             } catch (InterruptedException e) {
  24.                 e.printStackTrace();
  25.             } finally {
  26.                 lock.unlock();
  27.             }
  28.         }
  29.     });
  30.     threadB.start();
  31.     // 等待线程都启动完毕
  32.     Thread.sleep(1000);
  33.     System.out.println("查询是否有线程正在等待:" + lock.hasQueuedThreads());
  34.     System.out.println("查询处于等待的线程数:" + lock.getQueueLength());
  35.     System.out.println("threadA 是否处于等待状态:" +  lock.hasQueuedThread(threadA));
  36.     System.out.println("threadB 是否处于等待状态:" +  lock.hasQueuedThread(threadB));
  37. }
复制代码
输出结果如下:
  1. 查询是否有线程正在等待:true
  2. 查询处于等待的线程数:1
  3. threadA 是否处于等待状态:false
  4. threadB 是否处于等待状态:true
复制代码
从日志上可以清晰的看到,线程threadA先获取了锁,线程threadB处于等待获取锁的状态,处于等待的线程数为1。
2.2.7、hasWaiters 方法

hasWaiters()和getWaitQueueLength()方法,支持传入condition条件对象进行查询。
其中hasWaiters()方法表示查询是否存在正在等待并符合相关给定条件的线程;getWaitQueueLength()方法表示查询正在等待并符合相关给定条件的线程数量。
我们看一个简单的示例,如下:
  1. public static void main(String[] args) throws InterruptedException {
  2.     ReentrantLock lock = new ReentrantLock();
  3.     Condition condition = lock.newCondition();
  4.     Thread threadA = new Thread(new Runnable() {
  5.         @Override
  6.         public void run() {
  7.             lock.lock();
  8.             try {
  9.                 condition.await();
  10.                 System.out.println("await等待结束");
  11.             } catch (InterruptedException e) {
  12.                 e.printStackTrace();
  13.             } finally {
  14.                 lock.unlock();
  15.             }
  16.         }
  17.     });
  18.     threadA.start();
  19.     // 睡眠1秒
  20.     Thread.sleep(1000);
  21.     Thread threadB = new Thread(new Runnable() {
  22.         @Override
  23.         public void run() {
  24.             lock.lock();
  25.             try {
  26.                 System.out.println("是否存在正在等待并符合相关给定条件的线程:" + lock.hasWaiters(condition));
  27.                 System.out.println("正在等待并符合相关给定条件的线程数量:" + lock.getWaitQueueLength(condition));
  28.                 Thread.sleep(5000);
  29.                 condition.signal();
  30.                 System.out.println("signal 唤醒通知完毕");
  31.             } catch (InterruptedException e) {
  32.                 e.printStackTrace();
  33.             } finally {
  34.                 lock.unlock();
  35.             }
  36.         }
  37.     });
  38.     threadB.start();
  39. }
复制代码
输出结果如下:
  1. 是否存在正在等待并符合相关给定条件的线程:true
  2. 正在等待并符合相关给定条件的线程数量:1
  3. signal 唤醒通知完毕
  4. await等待结束
复制代码
需要注意的是,调用condition对象的方法,必须要在获取锁的方法体内执行。
三、小结

本文主要围绕ReentrantLock类的核心方法进行了一些知识总结,其中最常用方法的主要就两个,tryLock(long timeout, TimeUnit unit)和unlock(),通过它可以实现线程同步安全的效果。
本文内容比较多,如果有不正之处,请多多谅解,并欢迎批评指出。
四、参考

1、https://www.cnblogs.com/xrq730/p/4855538.html

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表