博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ReentrantLock简介
阅读量:6351 次
发布时间:2019-06-22

本文共 6120 字,大约阅读时间需要 20 分钟。

hot3.png

      ReentrantLock又名可重入锁,读音为[rɪ'entrənt],是entrant的同根词,拆分开来为“重复的-进入的-锁”。可重入指的是任意线程获取到某个锁,并且在该锁还未释放的情况下可再次获取到该锁。重入锁的实现主要需要解决两个问题:

  • 线程再次获取锁。当前线程获取锁的时候需要判断其是否为当前已经占据锁的线程,如果是则让其可再次获取到锁,否则会被阻塞;
  • 锁的最终释放。已经获取n次锁的线程需要进行n此release操作其他线程才能使用该锁。

如下示例展示了ReentrantLock的可重入特性:

public class Service {  private static final int TIMES = 2;  private Lock lock = new ReentrantLock();  public void reenter() {    for (int i = 0; i < TIMES; i++) {      lock.lock();      System.out.println(Thread.currentThread().getName() + " enter lock, times = " + i);    }    for (int i = 0; i < TIMES; i++) {      lock.unlock();      System.out.println(Thread.currentThread().getName() + " release lock, times = " + i);    }  }}

以下是主程序代码:

public class App {  public static void main(String[] args) {    Service service = new Service();    Runnable runnable = () -> service.reenter();    Thread threadA = new Thread(runnable, "thread A");    Thread threadB = new Thread(runnable, "thread B");    threadA.start();    threadB.start();  }}

运行结果如下:

thread A enter lock, times = 0thread A enter lock, times = 1thread A release lock, times = 0thread A release lock, times = 1thread B enter lock, times = 0thread B enter lock, times = 1thread B release lock, times = 0thread B release lock, times = 1

      可以看到线程A首先获取到锁,此时线程B被阻塞,而A在获取到锁之后再次获取了锁,这说明了ReentrantLock的可重入性,后面A两次释放锁之后B才能获取锁并执行相关代码。

      ReentrantLock除了是可重入的以外,在其构造方法中还可以传入一个boolean类型的参数,以表示当前的锁是公平锁还是非公平锁(默认是非公平锁)。在上述示例代码中,当某个线程调用lock.lock()方法获取锁之后,其余的线程将会被阻塞在该方法处,所谓的阻塞其实是指这些线程会按顺序进入一个阻塞队列,当获取锁的线程执行完毕释放锁之后(其可能再次尝试获取锁),阻塞队列中的锁会按照一定的规则从队列中弹出而获取锁。这里获取锁的规则则是分为公平和非公平两种方式,具体的定义如下:

  • 公平锁:所有尝试获取锁的线程都会被插入阻塞队列尾部,在拥有锁的线程执行完毕之后按照先进先出的规则从队列中弹出线程执行锁定代码;
  • 非公平锁:初次调用lock.lock()方法的线程以及在阻塞队列头的线程都有权争夺锁的控制权,如果获取到了则执行后续代码,否则其会被加入到阻塞队列尾部。

如下示例展示了ReentrantLock的公平性与非公平性:

public class Service {  private static final int TIMES = 3;  private final MyReentrantLock lock1 = new MyReentrantLock(true);  private final MyReentrantLock lock2 = new MyReentrantLock(false);  public void fair() {    testlock(lock1);  }  public void unfair() {    testlock(lock2);  }  private void testlock(MyReentrantLock lock) {    for (int i = 0; i < TIMES; i++) {      lock.lock();      try {        System.out.println("running: " + Thread.currentThread().getName() + "\twaiting: " + lock.printQueuedThreads());      } finally {        lock.unlock();      }    }  }  private static final class MyReentrantLock extends ReentrantLock {    private static final String EMPTY = "";    public MyReentrantLock(boolean fair) {      super(fair);    }    public String printQueuedThreads() {      List
threads = new ArrayList<>(super.getQueuedThreads()); Collections.reverse(threads); return printThreads(threads); } private String printThreads(List
threads) { StringBuilder result = new StringBuilder("["); threads.forEach(thread -> result.append(thread.getName()).append(", ")); if (result.length() == 1) { return EMPTY; } return result.delete(result.lastIndexOf(","), result.length()).append("]").toString(); } }}

以下是主程序代码:

public class App {  public static void main(String[] args) {    Service service = new Service();    Runnable fairTask = () -> service.fair();    Runnable unfairTask = () -> service.unfair();    execTask(fairTask, service, "fair");//    execTask(unfairTask, service, "unfair");  }  private static void execTask(Runnable task, Service service, String prefix) {    Thread thread1 = new Thread(task, prefix + " 1");    Thread thread2 = new Thread(task, prefix + " 2");    Thread thread3 = new Thread(task, prefix + " 3");    Thread thread4 = new Thread(task, prefix + " 4");    Thread thread5 = new Thread(task, prefix + " 5");    thread1.start();    thread2.start();    thread3.start();    thread4.start();    thread5.start();  }}

执行主程序代码,可得到类似以下运行结果:

running: fair 1	waiting: running: fair 2	waiting: [fair 3, fair 4, fair 5, fair 1]running: fair 3	waiting: [fair 4, fair 5, fair 1, fair 2]running: fair 4	waiting: [fair 5, fair 1, fair 2, fair 3]running: fair 5	waiting: [fair 1, fair 2, fair 3, fair 4]running: fair 1	waiting: [fair 2, fair 3, fair 4, fair 5]running: fair 2	waiting: [fair 3, fair 4, fair 5, fair 1]running: fair 3	waiting: [fair 4, fair 5, fair 1, fair 2]running: fair 4	waiting: [fair 5, fair 1, fair 2, fair 3]running: fair 5	waiting: [fair 1, fair 2, fair 3, fair 4]running: fair 1	waiting: [fair 2, fair 3, fair 4, fair 5]running: fair 2	waiting: [fair 3, fair 4, fair 5]running: fair 3	waiting: [fair 4, fair 5]running: fair 4	waiting: [fair 5]running: fair 5	waiting:

      可以看到,对于公平锁,每次执行的线程都和阻塞队列中阻塞线程的顺序是一致的。将主程序中执行公平锁的代码替换为非公平锁的代码,可得到以下结果:

running: unfair 1	waiting: running: unfair 4	waiting: [unfair 2, unfair 3]running: unfair 4	waiting: [unfair 2, unfair 3]running: unfair 4	waiting: [unfair 2, unfair 3]running: unfair 1	waiting: [unfair 2, unfair 3]running: unfair 1	waiting: [unfair 2, unfair 3]running: unfair 2	waiting: [unfair 3, unfair 5]running: unfair 2	waiting: [unfair 3, unfair 5]running: unfair 2	waiting: [unfair 3, unfair 5]running: unfair 3	waiting: [unfair 5]running: unfair 3	waiting: [unfair 5]running: unfair 3	waiting: [unfair 5]running: unfair 5	waiting: running: unfair 5	waiting: running: unfair 5	waiting:

      从结果中可以看出,获取锁的线程的顺序和在队列中的线程的顺序并不完全一致。公平锁的优点主要有两点:①每个线程都按照先后顺序执行锁定代码,不会出现某个线程等待时间过长的现象;②线程间锁竞争次数较少,降低了死锁的出现概率。但是公平锁的缺点也很明显,即由于每个线程是按照先后顺序执行任务,因而线程间切换的次数较高,这将消耗大量资源。相比较而言,非公平锁虽然出现死锁的概率较大,但这可以理解为代码问题,而不是锁的问题,另外,对于等待过长的线程,这个问题可以通过Condition的await()和signal()方法控制来减少阻塞队列中等待线程的数量,以此降低线程被长久阻塞的问题。相比较而言,非公平锁的优点则非常明显,通过上述实例代码可以看出,同样的代码,公平锁需要进行14次线程上下文环境的切换,而非公平锁只需要进行5次切换,这将极大的提高线程执行效率。

      相对于synchronized,Java的concurrent包中的相关组件为我们使用锁提供了非常大的灵活性,但是其也有一些固定的使用模式,而ReentrantLock作为Lock框架的一员,也需要遵循相关的使用方式,如下是使用Lock框架时的一个固定模式:

public class FixedConstruct {  private final Lock lock = new ReentrantLock();    public void method() {    lock.lock();    try {      // doSomething    } finally {      lock.unlock();    }  }}

      在调用lock.lock()方法时,当前锁只允许一个线程获取锁并执行后续代码,而其余的线程将会被阻塞,如果获取锁的线程抛出异常而退出了,那么此时锁是没有释放的,其他的线程也就会被一直阻塞。因而需要按照上述代码所示,在调用lock.lock()方法之后使用try...finally保证当前获取锁的线程肯定会释放锁。

转载于:https://my.oschina.net/zhangxufeng/blog/1507000

你可能感兴趣的文章
canvas系列教程08-canvas各种坑
查看>>
浅析package.json中的devdependencies 和 dependencies
查看>>
又一个 iOS 侧边栏组件: SideMenu
查看>>
Python每日一练0019
查看>>
vue.js 打包遇到的问题
查看>>
【译】更优秀的GraphQL官方中文文档-客户端如何使用
查看>>
git pull遇到的问题
查看>>
eclipse下maven spring项目环境配置
查看>>
无缝轮播
查看>>
CTS失败项分析(2)android.telephony.cts.VisualVoicemailServiceTest#testFilter_data
查看>>
三分钟,轻松了解Dapp
查看>>
GMQ交易平台满足不同客户群体的多种投资需求
查看>>
大数据开发如何入门你必须知道这些
查看>>
关于js(es5)如何优雅地创建对象
查看>>
阿里云前端周刊 - 第 28 期
查看>>
iOS 主队列同步造成死锁的原因
查看>>
es6 下比较对象是否有修改的简要方法
查看>>
windows安装mysql
查看>>
你还在看《深入理解Java虚拟机》的运行时数据模型吗?
查看>>
RIS,创建 React 应用的新选择
查看>>