ReentrantLock概述

相对于 synchronized 它具备如下特点

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待

与 synchronized 一样,都支持可重入

基本语法

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

1|0可重入

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

static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) { try { reentrantLock.lock(); m1(); } finally { reentrantLock.unlock(); } } public static void m1() { try { reentrantLock.lock(); m2(); } finally { reentrantLock.unlock(); } } public static void m2() { try { reentrantLock.lock(); } finally { reentrantLock.unlock(); } }

2|0可打断

直接看例子:

这样有效避免死锁的发生。

public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { /** * * 注意这里使用的是lockInterruptibly方法,如果使用lock.lock()方法,那么这里 * 等待的时候是不可以被打断的 */ log.info("尝试获取锁"); reentrantLock.lockInterruptibly(); //被其它使用interrupt的线程打断 } catch (InterruptedException e) { e.printStackTrace(); log.info("获取锁失败了"); // 这里如果出了错不要再往下执行了 return; } try { log.info("获取到锁了没有被打断!"); } finally { reentrantLock.unlock(); } }, "t1"); reentrantLock.lock(); t1.start(); Thread.sleep(222); // 打断线程thread,原本它的状态是在等待锁的,我们在它等待锁的时候打断了,不让它继续等待了 t1.interrupt(); log.info("主线程执行结束了 "); }

3|0锁超时

直接看例子线程获取锁指定等待时间超过了就会别打断:

public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { //lock.tryLock指定时间获取不到锁就会释放,lock.lock获取不到锁会无限等待 if (!lock.tryLock(2, TimeUnit.SECONDS)) {//false log.info("加锁失败了!"); // 这里如果出了错不要再往下执行了 return; } } catch (Exception e) { log.info("被打断啦"); e.printStackTrace(); //执行到这里失败就不要继续执行了 return; } try { log.info("执行完啦,获取到了锁,没被打断"); } finally { lock.unlock(); } }, "t1"); log.info("主线程获取锁"); lock.lock(); t1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("主线程释放锁"); lock.unlock(); log.info("线程运行结束"); }

使用锁超时解决哲学家就餐死锁问题:

@Slf4j public class RepastTest { public static void main(String[] args) { Chopstick2 c1 = new Chopstick2("1"); Chopstick2 c2 = new Chopstick2("2"); Chopstick2 c3 = new Chopstick2("3"); Chopstick2 c4 = new Chopstick2("4"); Chopstick2 c5 = new Chopstick2("5"); new Philosopher2("苏格拉底", c1, c2).start(); new Philosopher2("柏拉图", c2, c3).start(); new Philosopher2("亚里士多德", c3, c4).start(); new Philosopher2("赫拉克利特", c4, c5).start(); new Philosopher2("阿基米德", c5, c1).start(); } } @Slf4j(topic = "Philosopher") class Philosopher2 extends Thread{ Chopstick2 left; Chopstick2 right; public Philosopher2(String name, Chopstick2 left, Chopstick2 right) { super(name); this.left = left; this.right = right; } private void eat() { log.debug("eating..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { if (left.tryLock(2, TimeUnit.SECONDS)){ try { if (right.tryLock(2, TimeUnit.SECONDS)){ try { eat(); }finally { right.unlock(); } } }finally { left.unlock(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } } class Chopstick2 extends ReentrantLock { private String name ; public Chopstick2(String name) { this.name = name; } @Override public String toString() { return "Chopstick{" + "name='" + name + '\'' + '}'; } }

4|0公平锁

synchronized锁中,在entrylist等待的锁在竞争时不是按照先到先得来获取锁的,所以说synchronized锁时不公平的;ReentranLock锁默认是不公平的,但是可以通过设置实现公平锁。本意是为了解决之前提到的饥饿问题,但是公平锁一般没有必要,会降低并发度,使用trylock也可以实现。

设置:只要把构造器设置为true即可

源码: /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

5|0条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

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

使用要点:

  1. await 前需要获得锁
  2. await 执行后,会释放锁,进入 conditionObject 等待
  3. await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,执行唤醒的线程爷必须先获得锁
  4. 竞争 lock 锁成功后,从 await 后继续执行
static Lock lock = new ReentrantLock(); static Condition waitCigaretteQueue = lock.newCondition(); static Condition waitBreakfastQueue = lock.newCondition(); static volatile boolean hasCigarette = false; static volatile boolean hasBreakfast = false; public static void main(String[] args) { Thread thread = new Thread(() -> { try { lock.lock(); while (!hasCigarette) { try { waitBreakfastQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.info("等到他的烟。"); } finally { lock.unlock(); } }, "等烟线程"); thread.start(); Thread thread2 = new Thread(() -> { try { lock.lock(); while (!hasBreakfast) { try { waitCigaretteQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } log.info("等到早餐!"); } } catch (Exception e) { e.printStackTrace(); lock.unlock(); } }, "等早餐线程"); thread2.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } sendCigarette(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } sendBreakfast(); } public static void sendCigarette() { try { lock.lock(); log.info("烟来了"); hasCigarette = true; waitCigaretteQueue.signal(); } finally { lock.unlock(); } } public static void sendBreakfast() { lock.lock(); try { log.info("送早餐来了"); hasBreakfast = true; waitBreakfastQueue.signal(); } finally { lock.unlock(); } }

6|0同步模式之顺序控制

  1. 固定运行顺序,比如,必须先 2 后 1 打印

    1. wait notify 版
    public class OrderLock { // 用来同步的对象 static Object obj = new Object(); // t2 运行标记, 代表 t2 是否执行 static boolean t2runed = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (obj) { while (!t2runed) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(1); } }, "t1"); Thread t2 = new Thread(() -> { synchronized (obj) { t2runed = true; obj.notify(); } System.out.println(2); }, "t2"); t1.start(); t2.start(); } }
    1. Park Unpark 版
    /** * wait 和 notify的缺点 * 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 * wait * 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决 * 此问题 * 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个 * * park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, * 不需要『同步对象』和『运行标记』,不存在一个多个线程同时等待一个对象锁的现象,因为每个线程调用park的结果是等待它自己线程的upark来解锁 */ public class Test36 { public static void main(String[] args) { Thread t1 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { } // 当没有『许可』时,当前线程暂停运行;有『许可』时,用掉这个『许可』,当前线程恢复运行 LockSupport.park(); System.out.println("1"); }); Thread t2 = new Thread(() -> { System.out.println("2"); // 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』) LockSupport.unpark(t1); }); t1.start(); t2.start(); } }

    使用notify和wait实现循环打印abcabc

    public class WaitNotify { /** * 使用notify和wait实现循环打印abcabc * 交替打印ABC * 输出内容: 等待标记: 下一个标记: * A 1 2 * B 2 3 * C 3 1 */ public static void main(String[] args) { WaitNotifyDemo waitNotify = new WaitNotifyDemo(1, 15); new Thread(() -> { waitNotify.print("a", 1, 2); }, "线程一").start(); new Thread(() -> { waitNotify.print("b", 2, 3); }, "线程二").start(); new Thread(() -> { waitNotify.print("c", 3, 1); }, "线程三").start(); } } class WaitNotifyDemo { int flag; int loopNumber; public WaitNotifyDemo(int flag, int loopNumber) { this.flag = flag; this.loopNumber = loopNumber; } /** * @param str 要打印的内容 * @param flag 线程的打印标记 * @param nextFlag 下一个打印标记 */ public void print(String str, int flag, int nextFlag) { for (int i = 0; i < loopNumber; i++) { synchronized (this) { while (this.flag != flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //标记相同则打印 System.out.println(str); this.flag = nextFlag; // 修改了打印标记,唤醒其它线程让他们抢啦! this.notifyAll(); } } } }
  2. 交替输出,线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

    1. wait notify 版 Test37.java
    2. Lock 条件变量版
    public class AwaitSignalDemo { public static void main(String[] args) { AwaitSignal awaitSignal = new AwaitSignal(15); Condition aCondition = awaitSignal.newCondition(); Condition bCondition = awaitSignal.newCondition(); Condition cCondition = awaitSignal.newCondition(); new Thread(()->{ awaitSignal.print("a",aCondition,bCondition); },"线程一").start(); new Thread(()->{ awaitSignal.print("b",bCondition,cCondition); },"线程二").start(); new Thread(()->{ awaitSignal.print("c",cCondition,aCondition); },"线程三").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } awaitSignal.lock(); try { aCondition.signal(); }finally { awaitSignal.unlock(); } } } class AwaitSignal extends ReentrantLock { int loopNUmber; public AwaitSignal(int loopNUmber) { this.loopNUmber = loopNUmber; } public void print(String str, Condition current, Condition next) { for (int i = 0; i < loopNUmber; i++) { this.lock(); try { try { current.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(str); next.signal(); } finally { this.unlock(); } } } }
    1. Park Unpark 版
    public class Test39 { static Thread thread2 ; static Thread thread1; static Thread thread3; public static void main(String[] args) { ParkUnpark parkUnpark = new ParkUnpark(15); thread1 = new Thread(() -> { parkUnpark.print("a",thread2); }, "线程一"); thread2 = new Thread(() -> { parkUnpark.print("b",thread3); }, "线程二"); thread3 = new Thread(() -> { parkUnpark.print("c",thread1); }, "线程三"); thread1.start(); thread2.start(); thread3.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(thread1); } } class ParkUnpark{ int loopNumber; public ParkUnpark(int loopNumber) { this.loopNumber = loopNumber; } public void print(String str , Thread next ){ for (int i=0;i<loopNumber;i++){ LockSupport.park(); System.out.print(str); LockSupport.unpark(next); } } }

__EOF__

本文作者金融融融融果果
本文链接https://www.cnblogs.com/jinronga/p/14503108.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   金融融融融果果  阅读(116)  评论(2编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
历史上的今天:
2020-03-08 ArrayList的常用方法
2020-03-08 关于StringBuffer和StringBuilder的使用
点击右上角即可分享
微信分享提示