当前位置:   article > 正文

ReentrantLock

ReentrantLock

什么是ReentrantLock?

这是标准库给我们提供的另一种锁,前面我们学到的一种是synchronized

顾名思义,Reentrant:可重入的 但我们也知道,synchronized锁不也是可重入的嘛?

那这个锁到底有什么其他的过人之处呢?


传统加解锁:

synchroinzed加锁操作是基于代码块的方式加解锁的(在代码块内部时即为加锁状态,执行完代码块就释放锁,整个操作都是自动的)

reentrantLock加解锁状态就十分传统:

lock():加锁

unlock():解锁

这样的写法的好处就是可以让代码更灵活可变

若一段代码的临界区运行时间很长,如果使用synchronized,那么其他没有获取到锁对象的线程就会阻塞很长一段时间,但如果使用reentrantLock,即可以指定哪个时间段就可以释放锁对象,不会出现阻塞很长一段时间的场景,其加锁解锁完全由具体实际情况操作


但这样写的坏处为:非常害怕程序执行不到unlock方法,那么就无法解锁

比如程序在unlock之前抛出异常或者return时

解决方法:

将unlock放进finally代码块中:必会被执行到


可重入的:

  1. public static void main(String[] args) throws InterruptedException {
  2. ReentrantLock lock = new ReentrantLock();
  3. System.out.println("main线程获取锁对象");
  4. try{
  5. lock.lock();
  6. Thread.sleep(100);
  7. testReentrant();
  8. }finally {
  9. System.out.println("main线程释放锁");
  10. lock.unlock();
  11. }
  12. }
  13. public static void testReentrant(){
  14. try{
  15. lock.lock();
  16. System.out.println("test方法获取锁对象");
  17. }finally {
  18. System.out.println("test方法释放锁");
  19. lock.unlock();
  20. }
  21. }


可打断的:

lockInterruptibly()

什么叫做可打断的?

我们知道,在synchronized锁中,没有竞争到锁资源的线程会进入阻塞队列中,直到锁对象被释放才会重新尝试获取锁,在这个阻塞等待锁资源的时间段里线程是不能被打断的,也就是你调入此线程的interrupt方法没有什么作用

在ReentrantLock中,调用lockInterruptibly方法后,此时锁具有可打断性,如果线程在阻塞等待时,有其他线程调入此线程的interrupt方法,此时线程会被唤醒,且抛出异常


先验证synchroinzed不可打断性

  1. public static void main(String[] args) throws InterruptedException {
  2. // ReentrantLock的可打断性:在阻塞等待过程中 如果被其他线程调用了interrupt方法,就会唤醒并抛出异常
  3. // 果有其他线程调用了该线程的interrupt()方法进行中断操作,那么该线程将被唤醒,并抛出InterruptedException异常
  4. //
  5. //先验证synchronized锁的不可打断性
  6. Object loker = new Object();
  7. Thread t1 = new Thread(() -> {
  8. synchronized (loker) {
  9. System.out.println("t1线程获得锁,t1线程没有被打断");
  10. }
  11. });
  12. t1.start();
  13. synchronized (loker) {
  14. System.out.println("主线程拿到锁");
  15. //此时t1线程没有获取锁对象,处于阻塞队列中
  16. System.out.println("打断t1线程");
  17. //线程1并没有被唤醒抛出异常
  18. t1.interrupt();
  19. Thread.sleep(100);
  20. }
  21. }


lockInterruptibly():可打断性

  1. public static void main(String[] args) throws InterruptedException {
  2. //验证reentrantLock锁的可打断性
  3. ReentrantLock lock = new ReentrantLock();
  4. Thread t = new Thread(() -> {
  5. for (int i = 0; i < 2; i++) {
  6. try {
  7. System.out.println("t线程尝试获取锁");
  8. //锁的可打断性
  9. lock.lockInterruptibly();
  10. System.out.println("t线程拿到锁");
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  14. System.out.println("线程被打断唤醒,没有获取锁对象,后续的如何操作由程序员代码决定");
  15. //打印一个HelloWorld
  16. System.out.println("HelloWorld");
  17. System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  18. }
  19. }
  20. try {
  21. lock.lock();
  22. System.out.println("获取到锁对象");
  23. } finally {
  24. System.out.println("t线程释放锁对象");
  25. lock.unlock();
  26. }
  27. });
  28. System.out.println("主线程获取锁");
  29. lock.lock();
  30. Thread.sleep(100);
  31. //此时线程t处在阻塞队列
  32. t.start();
  33. System.out.println("主线程打断t1线程");
  34. t.interrupt();
  35. System.out.println("主线程释放锁");
  36. lock.unlock();
  37. }

可以看到在主线程对t线程执行interrupt方法后,t线程被唤醒且抛出异常其里边的内容依然继续执行


tryLock:

判断是否成功获取锁对象

  1. public static void main(String[] args) {
  2. //tryLock 是否成功获取到锁
  3. ReentrantLock lock = new ReentrantLock();
  4. System.out.println(lock.tryLock()); // true 主线程获取锁
  5. Thread t1 = new Thread(() -> {
  6. //此时主线程已经占用锁了 走else
  7. if(lock.tryLock()){
  8. System.out.println("线程获取到锁对象");
  9. }else{
  10. System.out.println("立即返回");
  11. return;
  12. }
  13. try{
  14. lock.lock();
  15. }catch (Exception e){
  16. e.printStackTrace();
  17. }finally {
  18. lock.unlock();
  19. }
  20. });
  21. t1.start();
  22. }


设置最大等待时间

  1. public static void main(String[] args) throws InterruptedException {
  2. //tryLock 是否成功获取到锁
  3. ReentrantLock lock = new ReentrantLock();
  4. System.out.println("主线程获取锁:"+lock.tryLock()); // true 主线程获取锁
  5. Thread t1 = new Thread(() -> {
  6. //此时主线程已经占用锁了 走else
  7. try {
  8. //最多等待3s
  9. if(lock.tryLock(3, TimeUnit.SECONDS)){
  10. System.out.println("线程获取到锁对象");
  11. lock.lock();
  12. }else{
  13. System.out.println("t1线程立即返回");
  14. return;
  15. }
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }finally {
  19. System.out.println("线程释放锁资源");
  20. lock.unlock();
  21. }
  22. });
  23. t1.start();
  24. Thread.sleep(2000);
  25. //休眠2s后 主线程是否锁资源
  26. lock.unlock();
  27. }

由于最大等待时间为3s,主线程休眠2s后就释放了锁资源,故线程可以拿到锁资源


公平锁:

需要在创建ReentrantLock锁对象时给出true参数-----fair

我们知道synchronized是非公平锁,线程抢占式调度,没有调度规则

而reentrantLock实现了公平锁,根据线程阻塞时间的长度来决定线程获取锁资源的优先级阻塞时间越长的线程优先级越高

  1. public static void main(String[] args) throws InterruptedException {
  2. //公平锁 fair
  3. ReentrantLock lock = new ReentrantLock(true);
  4. //阻塞时间越长的优先级越高
  5. Thread t1 = new Thread(() -> {
  6. try{
  7. lock.lock();
  8. System.out.println("t1获取锁对象");
  9. }finally {
  10. System.out.println("t1释放锁对象");
  11. lock.unlock();
  12. }
  13. });
  14. Thread t2 = new Thread(() -> {
  15. try{
  16. lock.lock();
  17. System.out.println("t2获取锁对象");
  18. }finally {
  19. System.out.println("t2释放锁对象");
  20. lock.unlock();
  21. }
  22. });
  23. Thread t3 = new Thread(() -> {
  24. try{
  25. lock.lock();
  26. System.out.println("t3获取锁对象");
  27. }finally {
  28. System.out.println("t3释放锁对象");
  29. lock.unlock();
  30. }
  31. });
  32. //主线程获取锁
  33. System.out.println("主线程获取锁资源");
  34. lock.lock();
  35. //休眠1s后 开启t1线程
  36. Thread.sleep(1000);
  37. t1.start();
  38. //休眠2s后,开启t2线程
  39. Thread.sleep(1000);
  40. t2.start();
  41. //休眠3s后,开启t3线程
  42. Thread.sleep(1000);
  43. t3.start();
  44. System.out.println("主线程释放锁对象");
  45. lock.unlock();
  46. //按照逻辑 执行顺序应该是 t1 t2 t3 ----- 等待时间: t1 3s t2 2s t3 1s
  47. }

公平锁的优势:保证所有线程都可以获取到锁资源,不会饿死在阻塞队列中(一直阻塞等待)

               劣势:吞吐量降低


Condition:

synchroinze锁中,可以通过Object类的wait/notify方法来决定线程执行的先后顺序,在synchroinzed中,只有一个waitSet等待队列

而在ReentrantLock中,使用Condition类下的signal和await方法来实现等待/唤醒操作。

每一个Condition都是一个等待队列,所以在ReentrantLock下一个锁可以对应多个Condition,这也就说明了更加细分了其目的性:为什么要wait:为什么要notify


Condition对象可以通过ReentrantLock的newCondition方法来获取

await():
  1. public static void main(String[] args) throws InterruptedException {
  2. ReentrantLock lock = new ReentrantLock();
  3. //Condition
  4. Condition condition = lock.newCondition();
  5. System.out.println("main获取到锁对象");
  6. lock.lock();
  7. System.out.println("执行await方法");
  8. condition.await();
  9. System.out.println("能够执行到这里吗?");
  10. }

可以看到主线程被阻塞,无法执行到await下方的代码。


signal():
  1. public static void main(String[] args) throws InterruptedException {
  2. //await:使线程进入阻塞队列 释放锁资源
  3. //signal:唤醒被await的线程 ps:并不会立马执行被唤醒的线程 而是将当前的任务执行完 类似于synchronized代码块中的notify后也是先把代码块中的内容执行完
  4. ReentrantLock lock = new ReentrantLock();
  5. //Condition
  6. Condition condition = lock.newCondition();
  7. Thread t1 = new Thread(() -> {
  8. try{
  9. lock.lock();
  10. System.out.println("t1获取锁对象");
  11. System.out.println("执行await方法 进入等待 释放锁资源");
  12. condition.await();
  13. System.out.println("t1线程被唤醒");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }finally {
  17. System.out.println("t1释放锁对象");
  18. lock.unlock();
  19. }
  20. });
  21. t1.start();
  22. Thread.sleep(4000);
  23. lock.lock();
  24. //main执行signal方法 唤醒t1线程 锁资源 t1并不会立马执行 而是等主线程执行完
  25. condition.signal();
  26. System.out.println("main线程继续正常向下执行");
  27. System.out.println("main线程释放锁资源");
  28. lock.unlock();
  29. }

我们可以发现,在调用signal方法后,会唤醒线程,但并不会立马执行此线程,而是将当前的内容执行完并释放锁资源后,才会执行被唤醒的线程


awaitUninterruptibly():

await操作后不可被打断

先来看看没有加此方法 ---- 可被打断

  1. public static void main(String[] args) throws InterruptedException {
  2. //awaitUninterruptibly()
  3. ReentrantLock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. Thread t1 = new Thread(() -> {
  6. lock.lock();
  7. System.out.println("获取锁资源");
  8. try {
  9. condition.await();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. System.out.println("线程被打断");
  13. }finally {
  14. System.out.println("释放锁资源");
  15. lock.unlock();
  16. }
  17. });
  18. t1.start();
  19. Thread.sleep(100);
  20. System.out.println("主线程打断t1线程");
  21. t1.interrupt();
  22. }

我们可以看到,线程被打断后提前唤醒,依然会执行任务中没有完成的内容,与上面类似


加了awaitUninterruptibly()方法:

  1. public static void main(String[] args) throws InterruptedException {
  2. //awaitUninterruptibly()
  3. ReentrantLock lock = new ReentrantLock();
  4. Condition condition = lock.newCondition();
  5. Thread t1 = new Thread(() -> {
  6. lock.lock();
  7. System.out.println("获取锁资源");
  8. try {
  9. condition.awaitUninterruptibly();
  10. } finally {
  11. System.out.println("释放锁资源");
  12. lock.unlock();
  13. }
  14. });
  15. t1.start();
  16. Thread.sleep(100);
  17. System.out.println("主线程打断t1线程");
  18. t1.interrupt();
  19. }

此时线程即使被打断也没有抛出异常


 awaitNanos(long nanosTimeout)

规定最大等待时间  超过此时间自动唤醒 单位:纳秒

  1. public static void main(String[] args) throws InterruptedException {
  2. ReentrantLock lock = new ReentrantLock();
  3. Condition condition = lock.newCondition();
  4. Thread t1 = new Thread(() -> {
  5. lock.lock();
  6. try {
  7. //5s
  8. condition.awaitNanos(5000000000L);
  9. System.out.println("线程自动唤醒");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }finally {
  13. System.out.println("t1线程释放锁对象");
  14. lock.unlock();
  15. }
  16. });
  17. t1.start();
  18. Thread.sleep(5100);
  19. System.out.println("main线程获取锁对象");
  20. lock.lock();
  21. System.out.println(lock.isHeldByCurrentThread());
  22. lock.unlock();
  23. System.out.println("main线程释放锁资源");
  24. }

当线程wait的时间超过最大指定时间后,会被自动唤醒

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/472914
推荐阅读
相关标签
  

闽ICP备14008679号