可重入锁ReentranLock:

ReentranLock 显示锁 (lock.lock())
synchronizer 隐式锁
一个线程在持有一个锁的时候,它内部能否再次(多次)申请该锁。如果一个线程已经获得了锁,其内部还可以多次申请该锁成功。那么我们就称该锁为可重入锁。

  1. void methodA(){
  2. lock.lock(); // 获取锁
  3. methodB();
  4. lock.unlock() // 释放锁
  5. }
  6. void methodB(){
  7. lock.lock(); // 获取锁
  8. // 其他业务
  9. lock.unlock();// 释放锁
  10. }

可重入锁可以理解为锁的一个标识。该标识具备计数器功能。标识的初始值为0,表示当前锁没有被任何线程持有。每次线程获得一个可重入锁的时候,该锁的计数器就被加1。每次一个线程释放该所的时候,该锁的计数器就减1。
前提是:当前线程已经获得了该锁,是在线程的内部出现再次获取锁的场景

image.png

可重入锁机制:https://www.cnblogs.com/1013wang/p/11820373.html

为什么要使用LockSupport?

传统的等待唤醒机制

  • 使用Object中的wait()方法让线程等待,使用Object中的notify方法唤醒线程
  • 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

Object类中wait()和notify()实现线程的等待唤醒

  • wait和notify方法必须要在同步块或同步方法里且成对出现使用。 wait和notify方法两个去掉同步代码块后看运行效果出现异常情况:
    • Exception in thread “A” Exception in thread “B”
    • java.lang.IllegalMonitorStateException
  • 先wait后notify才可以(如果先notify后wait会出现另一个线程一直处于等待状态)
  • synchronized关键字是属于JVM层面。monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象,只能在同步块或方法中才能调用wait/notify方法)

juc强大的三个工具类

  1. CountDownLatch(闭锁)

    1. CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
    2. 其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
    3. 计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

      1. //需求:要求6个线程都执行完了,mian线程最后执行
      2. public class CountDownLatchDemo {
      3. public static void main(String[] args) throws Exception{
      4. CountDownLatch countDownLatch=new CountDownLatch(6);
      5. for (int i = 1; i <=6; i++) {
      6. new Thread(()->{
      7. System.out.println(Thread.currentThread().getName()+"\t");
      8. countDownLatch.countDown();
      9. },i+"").start();
      10. }
      11. countDownLatch.await();
      12. System.out.println(Thread.currentThread().getName()+"\t班长关门走人,main线程是班长");
      13. }
      14. }

      利用枚举减少if else的判断

      1. public enum CountryEnum {
      2. one(1,"齐"),two(2,"楚"),three(3,"燕"),
      3. four(4,"赵"),five(5,"魏"),six(6,"韩");
      4. private Integer retCode;
      5. private String retMessage;
      6. private CountryEnum(Integer retCode,String retMessage){
      7. this.retCode=retCode;
      8. this.retMessage=retMessage;
      9. }
      10. public static CountryEnum getCountryEnum(Integer index){
      11. CountryEnum[] countryEnums = CountryEnum.values();
      12. for (CountryEnum countryEnum : countryEnums) {
      13. if(countryEnum.getRetCode()==index){
      14. return countryEnum;
      15. }
      16. }
      17. return null;
      18. }
      19. public Integer getRetCode() {
      20. return retCode;
      21. }
      22. public String getRetMessage() {
      23. return retMessage;
      24. }
      25. }

      ```java /国,被灭 魏 国,被灭 赵 国,被灭 燕 国,被灭 齐 国,被灭 韩 国,被灭 main *秦国一统江湖

  • */ public class CountDownLatchDemo { public static void main(String[] args) throws Exception{

      CountDownLatch countDownLatch=new CountDownLatch(6);
      for (int i = 1; i <=6; i++) {
          new Thread(()->{
              System.out.println(Thread.currentThread().getName()+"\t"+"**国,被灭");
              countDownLatch.countDown();
          },CountryEnum.getCountryEnum(i).getRetMessage()).start();
    
      }
      countDownLatch.await();
      System.out.println(Thread.currentThread().getName()+"\t"+"**秦国一统江湖");
    

    } } ```

  1. CyclicBarrier

    1. CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法

      //集齐7颗龙珠就能召唤神龙
      public class CyclicBarrierDemo {
        public static void main(String[] args) {
            // public CyclicBarrier(int parties, Runnable barrierAction) {}
            CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
                System.out.println("召唤龙珠");
            });
            for (int i = 1; i <=7; i++) {
                final int temp=i;
                new Thread(()->{
                    System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"颗龙珠");
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
      
        }
      }
      
  2. Semaphore(信号量)

    1. acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
    2. release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
    3. 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
      public class SemaphoreDemo {
        public static void main(String[] args) {
            Semaphore semaphore=new Semaphore(3);
            for (int i = 1; i <=6; i++) {
                new Thread(()->{
                    try {
                        System.out.println(Thread.currentThread().getName()+"\t抢占了车位");
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName()+"\t离开了车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        semaphore.release();
                    }
                },String.valueOf(i)).start();
            }
        }
      }
      

LockSupport详解

  1. 什么是LockSupport?
    1. 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
    2. LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在仍以位置阻塞,阻塞之后也有对应的唤醒方法。 LockSupport调用的Unsafe中的native代码。
    3. 官网:
      1. LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
      2. 使用了名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可 , permit只有两个值 0和1, 默认是0
      3. 可以把许可看成是一种(0,1)信号量,但与Semaphore不同的是,许可的累加上限是1
  2. 阻塞方法

    1. permit默认是0,所以一开始调用park()方法, 当前线程就会阻塞,知道别的线程将当前线程permit的unpark方法 会被唤醒,然后会将permit再次设置为0并返回
    2. static void park(): 底层是unsafe类native方法
    3. static void park(Object blocker)
  3. 唤醒方法

    1. 调用unpark(thread)方法后,会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。
    2. static void unpark()
  4. LockSupport它能解决的痛点

    1. LockSupport不用持有锁块,不用加锁,程序性能好
    2. 先后顺序,不容易导致卡死(因为unpark活得一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,将线程释放)

有关面试:

  1. 为什么可以先唤醒线程后阻塞线程?

     因为unpark活得一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,不会阻塞线程。
    
  2. 为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?

因为凭证的数量最多为1,连续调用两次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行

AbstractQueuedSynchronizer之AQS锁

AQS是什么?

  1. 用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石。通过内置的CLH(FIFO)队列的变种来完成资源获取线程的排队工作,将每条将要区抢占资源的线程封装成一个Node节点来实现锁的分配,有一个int类变量表示持有锁的状态,通过CAS完成对status值的修改(0表示没有,1表示阻塞)

image.png

  1. AQS为什么是JUC内容中最重要的基石
    1. ReentranLock
    2. CountDownLatch
    3. ReentrantReadWriteLock
    4. Semaphore

AQS内部架构图:
image.png