• ReentrantLock工作原理


    AQS原理分析
    什么是AQS
           java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于 AbstractQueuedSynchronizer(简称AQS) 实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的。
    • 一般是通过一个内部类Sync继承 AQS
    • 将同步器所有调用都映射到Sync对应的方法

    AQS具备的特性:
    • 阻塞等待队列
    • 共享/独占
    • 公平/非公平
    • 可重入
    • 允许中断
    AQS内部维护属性 volatile int state
    • state表示资源的可用状态
    • State三种访问方式:
    • getState()
    • setState()
    • compareAndSetState()
    AQS定义两种资源共享方式
    • Exclusive-独占,只有一个线程能执行,如ReentrantLock
    • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
    AQS定义两种队列
    • 同步等待队列: 主要用于维护获取锁失败时入队的线程
    • 条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁

    ReentrantLock

    ReentrantLock是一种基于AQS框架的应用实现 ,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized 是一种互斥锁,可以保证线程安全 。相对于 synchronized,ReentrantLock具备如下特点:
    • 可中断
    • 可以设置超时时间
    • 可以设置为公平锁
    • 支持多个条件变量
    • 与 synchronized 一样,都支持可重入

    synchronized和ReentrantLock的区别:

    • synchronized关键字,是JVM层次的锁实现,ReentrantLock是类,是JDK层次基于AQS的锁实现;二者都是可重入的独占锁。
    • synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过 ReentrantLock#isLocked判断;
    • synchronized自动加解锁,ReentrantLock要手动加解锁,它的操作更加灵活。
    • synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;ReentrantLock要保证公平性也会引入额外的开销,导致吞吐量下降,慎用。
    • synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
    • 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
    • ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活;
    • synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;
    • 在低竞争情况下synchronized的性能优于ReentrantLock。在高并发下,synchronized操作monitor涉及线程的用户态、内核态的切换,性能不如ReentrantLock。

     ReentrantLock的使用

    同步执行,类似于synchronized

    1. private static int sum = 0;
    2. private static Lock lock = new ReentrantLock();
    3. //private static TulingLock lock = new TulingLock();
    4. public static void main(String[] args) throws InterruptedException {
    5. for (int i = 0; i < 3; i++) {
    6. Thread thread = new Thread(()->{
    7. //加锁
    8. lock.lock();
    9. try {
    10. // 临界区代码
    11. // TODO 业务逻辑:读写操作不能保证线程安全
    12. for (int j = 0; j < 10000; j++) {
    13. sum++;
    14. }
    15. } finally {
    16. // 解锁
    17. lock.unlock();
    18. }
    19. });
    20. thread.start();
    21. }
    22. Thread.sleep(2000);
    23. System.out.println(sum);
    24. }

    可重入

    1. public static ReentrantLock lock = new ReentrantLock();
    2. public static void main(String[] args) {
    3. method1();
    4. }
    5. public static void method1() {
    6. lock.lock();
    7. try {
    8. log.debug("execute method1");
    9. method2();
    10. } finally {
    11. lock.unlock();
    12. }
    13. }
    14. public static void method2() {
    15. lock.lock();
    16. try {
    17. log.debug("execute method2");
    18. method3();
    19. } finally {
    20. lock.unlock();
    21. }
    22. }
    23. public static void method3() {
    24. lock.lock();
    25. try {
    26. log.debug("execute method3");
    27. } finally {
    28. lock.unlock();
    29. }
    30. }

    可中断

    1. ReentrantLock lock = new ReentrantLock();
    2. Thread t1 = new Thread(() -> {
    3. log.debug("t1启动...");
    4. try {
    5. lock.lockInterruptibly();
    6. try {
    7. log.debug("t1获得了锁");
    8. } finally {
    9. lock.unlock();
    10. }
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. log.debug("t1等锁的过程中被中断");
    14. }
    15. }, "t1");
    16. lock.lock();
    17. try {
    18. log.debug("main线程获得了锁");
    19. t1.start();
    20. //先让线程t1执行
    21. Thread.sleep(1000);
    22. t1.interrupt();
    23. log.debug("线程t1执行中断");
    24. } finally {
    25. lock.unlock();
    26. }

    锁超时

    1. ReentrantLock lock = new ReentrantLock();
    2. Thread t1 = new Thread(() -> {
    3. log.debug("t1启动...");
    4. // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
    5. // if (!lock.tryLock()) {
    6. // log.debug("t1获取锁失败,立即返回false");
    7. // return;
    8. // }
    9. //超时
    10. try {
    11. if (!lock.tryLock(1, TimeUnit.SECONDS)) {
    12. log.debug("等待 1s 后获取锁失败,返回");
    13. return;
    14. }
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. return;
    18. }
    19. try {
    20. log.debug("t1获得了锁");
    21. } finally {
    22. lock.unlock();
    23. }
    24. }, "t1");
    25. lock.lock();
    26. try {
    27. log.debug("main线程获得了锁");
    28. t1.start();
    29. //先让线程t1执行
    30. Thread.sleep(2000);
    31. } finally {
    32. lock.unlock();
    33. }

    公平锁

    1. //ReentrantLock lock = new ReentrantLock(true); //公平锁
    2. ReentrantLock lock = new ReentrantLock(); //非公平锁
    3. for (int i = 0; i < 500; i++) {
    4. new Thread(() -> {
    5. lock.lock();
    6. try {
    7. try {
    8. Thread.sleep(10);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. log.debug(Thread.currentThread().getName() + " running...");
    13. } finally {
    14. lock.unlock();
    15. }
    16. }, "t" + i).start();
    17. }
    18. // 1s 之后去争抢锁
    19. Thread.sleep(1000);
    20. for (int i = 0; i < 500; i++) {
    21. new Thread(() -> {
    22. lock.lock();
    23. try {
    24. log.debug(Thread.currentThread().getName() + " running...");
    25. } finally {
    26. lock.unlock();
    27. }
    28. }, "强行插入" + i).start();
    29. }

    条件变量

    1. private static ReentrantLock lock = new ReentrantLock();
    2. private static Condition cigCon = lock.newCondition();
    3. private static Condition takeCon = lock.newCondition();
    4. private static boolean hashcig = false;
    5. private static boolean hastakeout = false;
    6. //送烟
    7. public void cigratee(){
    8. lock.lock();
    9. try {
    10. while(!hashcig){
    11. try {
    12. log.debug("没有烟,歇一会");
    13. cigCon.await();
    14. }catch (Exception e){
    15. e.printStackTrace();
    16. }
    17. }
    18. log.debug("有烟了,干活");
    19. }finally {
    20. lock.unlock();
    21. }
    22. }
    23. //送外卖
    24. public void takeout(){
    25. lock.lock();
    26. try {
    27. while(!hastakeout){
    28. try {
    29. log.debug("没有饭,歇一会");
    30. takeCon.await();
    31. }catch (Exception e){
    32. e.printStackTrace();
    33. }
    34. }
    35. log.debug("有饭了,干活");
    36. }finally {
    37. lock.unlock();
    38. }
    39. }
    40. public static void main(String[] args) {
    41. ReentrantLockDemo6 test = new ReentrantLockDemo6();
    42. new Thread(() ->{
    43. test.cigratee();
    44. }).start();
    45. new Thread(() -> {
    46. test.takeout();
    47. }).start();
    48. new Thread(() ->{
    49. lock.lock();
    50. try {
    51. hashcig = true;
    52. log.debug("唤醒送烟的等待线程");
    53. cigCon.signal();
    54. }finally {
    55. lock.unlock();
    56. }
    57. },"t1").start();
    58. new Thread(() ->{
    59. lock.lock();
    60. try {
    61. hastakeout = true;
    62. log.debug("唤醒送饭的等待线程");
    63. takeCon.signal();
    64. }finally {
    65. lock.unlock();
    66. }
    67. },"t2").start();
    68. }

    ReentrantLock源码

    加锁:

    1.     CAS加锁,成功后将重入线程设置为当前线程。重入数加1。
    2.     如果加锁失败则将线程放入一个双向链表的等待队列,并调用unsafe.park()挂起线程。

    解锁:

    1.     直接将重入线程设置为null,将state还原为0。
    2.     调用unsafe.park()唤起等待队列中的一个线程去竞争锁。

  • 相关阅读:
    vue基础知识(三)
    Windows操作系统查看ip的方式和Linux系统查看ip的方式的区别
    驱动开发:内核远程线程实现DLL注入
    SQL Server安装
    [附源码]java毕业设计商务酒店管理系统
    LINUX初级 总结
    基于python的django框架博客论坛交流系统
    Python 算法交易实验44 实验笔记2
    PAT甲级打卡-1005-1010
    自动化测试下拉框选项定位报错
  • 原文地址:https://blog.csdn.net/lmj3732018/article/details/126079117