Java并发编程之ReentrantLock可重入锁的实例代码

 更新时间:2021年02月06日 14:21:04   作者:Java硬件工程师  
这篇文章主要介绍了Java并发编程之ReentrantLock可重入锁的实例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

目录 1.ReentrantLock可重入锁概述2.可重入3.可打断4.锁超时5.公平锁6.条件变量 Condition

1.ReentrantLock可重入锁概述

相对于 synchronized 它具备如下特点
可中断
synchronized锁加上去不能中断,a线程应用锁,b线程不能取消掉它
可以设置超时时间
synchronized它去获取锁时,如果对方持有锁,那么它就会进入entryList一直等待下去。而可重入锁可以设置超时时间,规定时间内如果获取不到锁,就放弃锁
可以设置为公平锁
防止线程饥饿的情况,即先到先得。如果争抢的人比较多,则可能会发生永远都得不到锁

支持多个条件变量多个waitset(不支持条件一的去a不支持条件二的去b)
synchronized只支持同一个waitset.
与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}

synchronized是在关键字的级别来保护临界区,而reentrantLock是在对象的级别保护临界区。临界区即访问共享资源的那段代码。finally中表明不管将来是否出现异常,都会释放锁,释放锁即调用unlock方法。否则无法释放锁,其它线程就永远也获取不了锁。

2.可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
ReentrantLock和synchronized都是可重入锁。

public class TestReentranLock1 {
 static ReentrantLock lock = new ReentrantLock();
 public static void main(String[] args) {
  method1();
 }
 public static void method1() {
  lock.lock();
  try {
   System.out.println("execute method1");
   method2();
  } finally {
   lock.unlock();
  }
 }
 public static void method2() {
  lock.lock();
  try {
   System.out.println("execute method2");
   method3();
  } finally {
   lock.unlock();
  }
 }
 public static void method3() {
  lock.lock();
  try {
   System.out.println("execute method3");
  } finally {
   lock.unlock();
  }
 }
}
execute method1
execute method2
execute method3

3.可打断

可打断是指在等待锁的过程中,其它线程可以用interrupt方法终止我的等待。synchronized锁是不可打断的。
我们要想在等锁的过程中被打断,就要使用lockInterruptibly()方法对lock对象加锁,而不是lock()方法

public class TestReentranLock2 {
 public static void main(String[] args) {
  ReentrantLock lock = new ReentrantLock();
  Thread t1 = new Thread(() -> {
   try {
    //如果没有竞争,此方法就会获取lock对象的锁
    //如果有竞争,就进入阻塞队列等待,可以被其它线程用interrupt打断
    System.out.println("尝试获得锁");
    lock.lockInterruptibly();
   } catch (InterruptedException e) {
    e.printStackTrace();
    System.out.println("等锁的过程中被打断");
    return;
   }
   try {
    System.out.println("t1获得了锁");
   } finally {
    lock.unlock();
   }
  }, "t1");
  lock.lock();
  System.out.println("主线程获得了锁");
  t1.start();
  try {
   try {
    sleep(1);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   t1.interrupt();
   System.out.println("执行打断t1");
  } finally {
   lock.unlock();
  }
 }
}
主线程获得了锁
尝试获得锁
执行打断t1
等锁的过程中被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at cn.yj.jvm.TestReentranLock2.lambda$main$0(TestReentranLock2.java:15)
	at java.lang.Thread.run(Thread.java:748)

注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断,即不是。即使用lock()方法。
这种方式可以避免死锁情况的发生,避免无休止的等待。

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
 System.out.println("启动...");
 lock.lock();
 try {
  System.out.println("获得了锁");
 } finally {
  lock.unlock();
 }
}, "t1");
lock.lock();
System.out.println("获得了锁");
t1.start();
try {
 sleep(1);
 t1.interrupt();
 System.out.println("执行打断");
 sleep(1);
} finally {
 System.out.println("释放了锁");
 lock.unlock();
}

4.锁超时

ReentranLock支持可打断,其实就是为了避免死等,这样就可以减少死锁的发生。实际上可打断这种方式属于一种被动的避免死等,是由其它线程interrupt来打断。
而锁超时是主动的方式避免死等的手段。
获取锁用tryLock()方法,即尝试获得锁,如果成功了,它就获得锁,如果失败了,它就可以不去进入阻塞队列等待,它就会返回false,表示没有获得锁

立刻失败

public static void main(String[] args) {
  ReentrantLock lock = new ReentrantLock();
  Thread t1 = new Thread(() -> {
   System.out.println("启动...");
   if (!lock.tryLock()) {
    System.out.println("获取不到锁,立刻失败,返回");
    return;
   }
   try {
    System.out.println("获得了锁");
   } finally {
    lock.unlock();
   }
  }, "t1");
  lock.lock();
  System.out.println("获得了锁");
  t1.start();
  try {
   try {
    sleep(500);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } finally {
   lock.unlock();
  }
}

获得了锁
启动...
获取不到锁,立刻失败,返回

超时失败
lock.tryLock(1,TimeUnit.SECONDS)表示尝试等待1s,如果主线程不释放锁,那么它就会返回false,如果释放了锁,那么它就会返回true.tryLock也支持被打断,被打断时报异常

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
 log.debug("启动...");
 try {
  if (!lock.tryLock(1, TimeUnit.SECONDS)) {
   log.debug("获取等待 1s 后失败,返回");
   return;
  }
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 try {
  log.debug("获得了锁");
 } finally {
  lock.unlock();
 }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
 sleep(2);
} finally {
 lock.unlock();
}

输出

18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回

5.公平锁

对于synchronized来说,它是不公平的锁。当一个线程持有锁,其他线程就会进入阻塞队列等待,当锁的持有者释放锁的时候,这些线程就会一拥而上,谁先抢到,谁就成为monitor的主人,而不会按照先来先得的规则。

ReentrantLock 默认是不公平的
ReentrantLock有一个带参构造方法。默认是非公平的。

 public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

 

我们可以通过布尔值改成真,来保证它的公平性。即将来阻塞队列里的线程,争抢锁的时候会按照进入阻塞队列的顺序执行,先到先得

6.条件变量 Condition

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

synchronized 是那些不满足条件的线程都在一间休息室等消息
而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
  • signal 相当于 notify,signalAll 相当于 notifyAll
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
 new Thread(() -> {
  try {
   lock.lock();
   while (!hasCigrette) {
    try {
     waitCigaretteQueue.await();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   log.debug("等到了它的烟");
  } finally {
   lock.unlock();
  }
 }).start();
 new Thread(() -> {
  try {
   lock.lock();
   while (!hasBreakfast) {
    try {
     waitbreakfastQueue.await();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   log.debug("等到了它的早餐");
  } finally {
   lock.unlock();
  }
 }).start();
 sleep(1);
 sendBreakfast();
 sleep(1);
 sendCigarette();
}
private static void sendCigarette() {
 lock.lock();
 try {
  log.debug("送烟来了");
  hasCigrette = true;
  waitCigaretteQueue.signal();
 } finally {
  lock.unlock();
 }
}
private static void sendBreakfast() {
 lock.lock();
 try {
  log.debug("送早餐来了");
  hasBreakfast = true;
  waitbreakfastQueue.signal();
 } finally {
  lock.unlock();
 }
}

输出

18:52:27.680 [main] c.TestCondition - 送早餐来了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送烟来了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟

到此这篇关于Java并发编程之ReentrantLock可重入锁的实例代码的文章就介绍到这了,更多相关Java ReentrantLock可重入锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • jdk7 中HashMap的知识点总结

    jdk7 中HashMap的知识点总结

    HashMap的原理是老生常谈了,不作仔细解说。一句话概括为HashMap是一个散列表,它存储的内容是键值对(key-value)映射。这篇文章主要总结了关于jdk7 中HashMap的知识点,需要的朋友可以参考借鉴,一起来看看吧。
    2017-01-01
  • Protobuf详解及入门指南附完整代码

    Protobuf详解及入门指南附完整代码

    Protobuf是一种由Google开发的二进制序列化格式,用于高效地序列化和反序列化结构化数据,它广泛应用于分布式系统、RPC框架和数据存储中,提供了高效性、简洁性、版本兼容性和语言无关性,本文介绍Protobuf详解及入门指南,感兴趣的朋友一起看看吧
    2025-03-03
  • 详解SpringMVC实现图片上传以及该注意的小细节

    详解SpringMVC实现图片上传以及该注意的小细节

    本篇文章主要介绍了详解SpringMVC实现图片上传以及该注意的小细节,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-02-02
  • Spring AOP实现原理解析

    Spring AOP实现原理解析

    这篇文章主要为大家详细介绍了Spring AOP的实现原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • SpringCloud启动eureka server后,没报错却不能访问管理页面(404问题)

    SpringCloud启动eureka server后,没报错却不能访问管理页面(404问题)

    这篇文章主要介绍了SpringCloud启动eureka server后,没报错却不能访问管理页面(404问题),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • 一篇文章带你了解Java SpringBoot Nacos

    一篇文章带你了解Java SpringBoot Nacos

    这篇文章主要介绍了SpringBoot使用Nacos配置中心的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-09-09
  • Java中遍历数组使用foreach循环还是for循环?

    Java中遍历数组使用foreach循环还是for循环?

    这篇文章主要介绍了Java中遍历数组使用foreach循环还是for循环?本文着重讲解for语句的语法并给出使用实例,同时总结出尽量使用foreach语句遍历数组,需要的朋友可以参考下
    2015-06-06
  • SpringBoot中@PostConstruct 注解的实现

    SpringBoot中@PostConstruct 注解的实现

    在Spring Boot框架中, @PostConstruct是一个非常有用的注解,它用于在依赖注入完成后执行初始化方法,本文将介绍 @PostConstruct的基本概念、使用场景以及提供详细的代码示例,感兴趣的可以了解一下
    2024-09-09
  • Spring Cloud Ribbon客户端详细介绍

    Spring Cloud Ribbon客户端详细介绍

    Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用
    2022-09-09
  • Java编码算法与哈希算法深入分析使用方法

    Java编码算法与哈希算法深入分析使用方法

    首先,我们一起来学习一下编码算法,举例说明,ASCII码就是我们常见的一种编码,字母a的编码是十六进制的0x61,字母b是0x62,以此类推。哈希算法,可被称为摘要算法。因此,哈希算法的加密是单向的,不可用密文解密得到明文
    2022-11-11

最新评论