基于ReentrantLock深入理解AQS数据结构

LockSupport、Synchronized、Condition

java中3种让线程等待唤醒的方法:

  • 使用Object的wait()让线程等待,使用Object中的notify方法唤醒线程;

  • 使用JUC中Condition的await()方法让线程等待,使用Signal()方法唤醒线程;

  • LockSupport可以阻塞当前线程以及唤醒指定被阻塞的线程;

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

  • Condition接口中的await和signal方法实现线程等待和唤醒;

LockSupport

  • 是一个线程阻塞工具类;可以让线程在任意位置阻塞,阻塞之后也有唤醒方法;底层调用Unsafe的native方法
  • 通过park()与unpark()来实现阻塞和唤醒线程操作;
  • 官网:LockSupport类使用一种名为Permit的概念来做到阻塞和唤醒线程功能,每个线程有一个permit,permit只有两个值1和0,默认为0;
    可以把Permit许可看成一种(0,1)的信号量;不同的是许可的累加上限为1;

阻塞方法park

底层是unsafe类native方法

public static void park() {
    UNSAFE.park(false, 0L);
}
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

唤醒方法unpark

  • 调用unpark(thread)方法后,就会将thread线程的许可permit设置为1(注意多次调用unpark方法,不会累加,permit值还是1),会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法立刻返回;
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

LockSupport解决的问题

1)不用持有锁块,不用加锁,程序性能好;
2)先后顺序,不容易卡死;(因为unpark获得了一个凭证,之后再调用park方法,既可以消费,不会阻塞)

/*
(1).阻塞
 (permit默认是O,所以一开始调用park()方法,
当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,
 park方法会被唤醒,然后会将permit再次设置为O并返回)
 static void park()
 static void park(Object blocker)
(2).唤醒
static void unpark(Thread thread)
 (调用unpark(thread)方法后,就会将thread线程的许可permit设置成1
(注意多次调用unpark方法,不会累加,
 permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回)
 static void unpark(Thread thread)
* */
public class LockSupportDemo {
    public static void main(String[] args) {

        Thread t1=new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
            LockSupport.park();
            /*
            如果这里有两个LockSupport.park(),因为permit的值为1,上一行已经使用了permit
            所以下一行被注释的打开会导致程序处于一直等待的状态
            * */
            //LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"\t"+"被B唤醒了");
            },"A");
        t1.start();

        //下面代码注释是为了A线程先执行
        //try { TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {e.printStackTrace();}

        Thread t2=new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t"+"唤醒A线程");
            //有两个LockSupport.unpark(t1),由于permit的值最大为1,所以只能给park一个通行证
            LockSupport.unpark(t1);
            //LockSupport.unpark(t1);
        },"B");
        t2.start();
    }
}
  • 每个线程最多只能对一个线程最多发放一次许可证permit,但是每个线程可以对多个线程发放一次许可证permit
  • 一个线程中如果有两个或者多个LockSupport的park方法:则需要多个线程进行发放许可证permit

AQS

  • 是用来构建锁或者其他同步器组件的重量级基础框架
  • 通过内置的CLH(FIFO)队列的变种来完成资源获取线程的排队工作,将每条要去抢占资源的线程封装成一个Node结点来实现锁的分配,有一个int类变量表示持有锁的状态,通过CAS完成对status的值的修改(0表示没有,1表示阻塞)
  • 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现,他将请求共享资源的线程封装成一个个Node装进队列,通过CAS,自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步效果。

AQS具备特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平锁
  • 可重入
  • 允许中断

除了ReentrantLock外,JUC下其他的同步器组件都是基于AQS框架实现的;
在这里插入图片描述

  • 一般通过定义内部类Sync继承AQS

AQS数据结构

  • state
    state表示资源的可用状态,如果大于0,则说明锁已经被占用了。如果等于0,则说明锁没有被占用。
  • head
    指向队列的头结点;
  • tail
    指向队列的尾结点;
  • Exclusive-独占,只有一个线程能执行,如ReentrantLock
  • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
  • AQS内部定义了两个队列
    1. 同步等待队列
    2. 条件等待队列

同步等待队列

AQS同步等待队列也称CLH队列。是一种双向链表数据结构的队列,按照FIFO先入先出获取锁;

在这里插入图片描述

条件等待队列

Condition是一个多线程协调通信的工具类,某些线程等待某个条件,当条件具备时,等待线程被唤醒,从而争夺锁;
在这里插入图片描述

AQS数据结构源码

在这里插入图片描述

 static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

      //结点的状态
        volatile int waitStatus;
	 //指向上一个结点
        volatile Node prev;
			//指向下一个结点
        volatile Node next;
 	 //将获取锁失败的线程封装为一个个的Node结点,这里封装了获取锁失败的线程;
        volatile Thread thread;
        Node nextWaiter;
		...
}

在这里插入图片描述

ReentrantLock开始解读AQS

在 ReentrantLock 内定义了静态内部类,分别为 NoFairSync(非公平锁)和 FairSync(公平锁)。
在这里插入图片描述
ReentrantLock的构造函数:不传参数默认创建一个非公平锁,参数为true为公平锁,参数为false为非公平锁。
代码如下:

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

以NonfairSync为例:
在这里插入图片描述
在 ReentrantLock 中,NoFairSync 和 FairSync 中 tryAcquire() 方法的区别,可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
在这里插入图片描述
hasQueuedPredecessors() 方法是公平锁加锁时判断等待队列中是否存在有效节点的方法

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
        //1.Node s= h.next;头结点的下一个结点
        //2. s==null 判断头结点的下一个结点是否为空节点
        //头节点指向的是哨兵结点,判断其下一个结点是否为空,为空则队列为空
        //3. || s.thread!=Thread.currentThread() 
        //判断哨兵结点的下一个结点封装的线程是否和当前线程相等。
}

篇幅太长、进入第二篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值