Lock接口方法介绍


AQS为锁提供了一系类的模板方法,结合队列管理,结合Lock接口的实现 我们针对这些模板方法做一些说明,分为独占模式和共享模式的方法,本文主要讲Lock的实现,但涉及到AQS的机制,看本文之前需要先了解AQS的工作机制

AQS介绍点击链接

Lock接口介绍

Lock接口的方法,具体功能的实现是依靠子类的实现


public interface Lock {
    
    

    // 获取锁,调用该方法时当前线程将会获取锁,当获得锁之后将会从该方法返回
    void lock();

    // 可中断获取锁,和lock()不同在于该方法会响应中断,在锁的获取中可以中断当前线程获取锁操作
    void lockInterruptibly() throws InterruptedException;

    // 尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false
    boolean tryLock();

    // 超时的获取锁,当线程出现以下3中情况时返回:
    // 1. 当前线程在超时时间内获得锁
    // 2. 当前线程在超时时间内被中断
    // 3. 超时时间结束,返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁
    void unlock();

    // 获取等待通知的组建,该组建和当前的锁绑定,当前线程只有获得了锁
    // 才能调用该组件的wait()方法,调用后,当前线程将释放锁
    Condition newCondition();
}

实现Lock接口的常见的有 独占模式的有重入锁ReentrantLock,写锁WriteLock。共享模式的有读锁ReadLock,

独占模式

已ReentrantLock的非公平锁为例

lock()

	// 步骤1 调用Lock方法
    public void lock() {
    
    
        sync.lock();
    }
    // 步骤2 调用Sync的lock方法,ReentrantLock两种模式公平非公平,这里用非公平的展示
    final void lock() {
    
    
      if (compareAndSetState(0, 1))//cas操作成功 就设置独占线程,不管队 非公平体现
                setExclusiveOwnerThread(Thread.currentThread());
       else
            acquire(1);//进入AQS方法
      }

acquire(1)方法,AQS的模板方法,将state管理 和 队列结合起来

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) && // 尝试获取写锁。失败则入队自旋挂起
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

进入acquireQueued方法才是获取锁的核心方法了,也是AQS的模板方法

    final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
            boolean interrupted = false; //设置记录 中断标识
            for (;;) {
    
    
                final Node p = node.predecessor(); // 获取前驱节点 并做判空了
                if (p == head && tryAcquire(arg)) {
    
     //前驱为头节点 当前线程再次尝试获取锁
                    setHead(node); // 成功了 设置当前节点为头节点
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //上述判断条件不成立,进入判断是否需要挂起线程操作 如需要挂起线程进入 &&后的方法
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) //如果进入到这个方法里,那代码就停留在这里了
                    interrupted = true;
            }
        } finally {
    
    
            if (failed)//正常情况failed = false 除非方法执行异常了,failed还是true才执行这里
                cancelAcquire(node);
        }
    }

被挂起的线程就停在parkAndCheckInterrupt那里,等待被唤醒,理论上lock方法会获取到锁,只是时间长短问题

void lockInterruptibly() throws InterruptedException

该方法 没有返回值,但是抛出中断异常,说明可以响应异常

    public void lockInterruptibly() throws InterruptedException {
    
    
        sync.acquireInterruptibly(1);//AQS方法
    }

acquireInterruptibly方法默认参数1

扫描二维码关注公众号,回复: 17540651 查看本文章
    public final void acquireInterruptibly(int arg)throws InterruptedException {
    
    
        if (Thread.interrupted()) // 检查当前线程是否中断 如果是直接抛出中断异常
            throw new InterruptedException();
        if (!tryAcquire(arg))//尝试获取资源:`tryAcquire(arg)`,如果成功,则直接返回。
            doAcquireInterruptibly(arg);
    }

步骤1 先判断是否中断,是直接抛出中断异常,到这里也就结束了,中断意思是中断线程某种操作,这里就中断了线程尝试获取锁的操作

线程运行--》调用lock系列方法尝试获取锁--》拿不到锁进入队列挂起 等待拿到锁--》拿到锁继续执行

在上面第二步如果中断了,就不往后了,线程原来该怎么运行还怎么运行,不接受入队啊锁什么的事

步骤2 再次尝试获取锁,拿不到在开始接受AQS的入队以及其他 管理 doAcquireInterruptibly方法

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
    
    
        final Node node = addWaiter(Node.EXCLUSIVE);//入队
        boolean failed = true;
        try {
    
    
            for (;;) {
    
    
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
    
    
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);
        }
    }

这个方法和上面的acquireQueued大体一致,区别就在acquireQueued不响应中断,而该方法响应异常

           if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//线程挂起 停留在这行不执行了
                    throw new InterruptedException();
            }

在parkAndCheckInterrupt方法里面做了 挂起线程操作 LockSupport.park(this);而对应的解挂线程的操作有LockSupport.unpark(this.thread),另外一种方式就是响应中断,很好理解,挂起线程是一种操作,在此之前设置中断标识意思 我要中断接下来的挂起操作,让你挂起操作芭比Keio

所以lockInterruptibly和lock方法区别就在于
lock方法 优先考虑获取锁 不可中断,会等待直到获得锁,当获得锁之后将会从该方法返回
lockInterruptibly优先考虑 响应中断,即使没有拿到锁也会因为中断而退出

boolean tryLock()

首先 这个方法是个布尔值类型的 返回的是ture或者false

        public boolean tryLock( ) {
    
    
            return sync.nonfairTryAcquire();//这里没有使用AQS模板方法
        }

我们还是看非公平nonfairTryAcquire

        final boolean nonfairTryAcquire(int acquires) {
    
    
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
    
     // 判断独占锁是否当前没有线程持有
                if (compareAndSetState(0, acquires)) {
    
    
                    setExclusiveOwnerThread(current); //设置独占线程结束
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
    
     //如果是当前线程 重入
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

这个方法实现比较简单的,独占锁就是判断当前是否允许获得获得,获得就更新设置独占线程,或者如果当前线程以及持有锁就累加1,如果不允许就返回false

该方法不涉及到什么入队管理等待什么,尝试获取锁,试一下呗,成功了拿到锁返回ture,失败了就返回false。有枣没枣打一杆子,成就成不成就不成呗,会立即给你个明确的结果

boolean tryLock(long time, TimeUnit unit) throws InterruptedException

这个 方法返回的也是一个布尔类型

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
    
    
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));//AQS方法
    }

tryAcquireNanos

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    
    
        if (Thread.interrupted())
            throw new InterruptedException(); //抛出中断异常 说明可以响应中断
        return tryAcquire(arg) || // 尝试获取锁失败 进入后面doAcquireNanos方法
            doAcquireNanos(arg, nanosTimeout);
    }

doAcquireNano方法

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    
    
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;//计算下截止时间
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
    
    
            for (;;) {
    
    
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
    
    
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime(); //计算下剩余时间
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) && //判断是否挂起操作
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);//定时挂起
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);
        }
    }

这个方法和acquireQueued方法很相似,多了三个东西
1.截止时间比较 nanosTimeout
2.规定时间的挂起线程 LockSupport.parkNanos(this, nanosTimeout)
3.响应中断

在上面我们讲了 线程的挂起就停留在那里不在执行了,等待后续的唤醒,而挂起线程的操作是线程park方法LockSupport.park(this),解挂方式两种1.unPark方法精准线程解挂 2.中断解挂
而这里挂起线程 LockSupport.parkNanos(this, nanosTimeout),除了上面两种解挂方式之外,该方法还定义了时间,到时间自动解挂

该方法作用:超时的获取锁,当线程出现以下3中情况时返回:

  1. 当前线程在超时时间内获得锁 返回ture
  2. 当前线程在超时时间内被中断 返回false
  3. 超时时间结束,返回false

由于nanosTimeout 设定是纳秒单位,所以也是很快就会给返回拿到锁 或者没有拿到锁的结果,只不过区别与tryLock方法的是,多了 规定时间内 和响应中断,不果如果线程在这里没有拿到锁,线程该执行还继续执行,当前线程加入队列的节点会触发finally 代码块清除掉

void unlock()

上面几个都是获取锁的方法,而对应的就是锁的释放方法

    public void unlock() {
    
    
        sync.release(1);//还是AQS方法
    }

release方法

    public final boolean release(int arg) {
    
    
        if (tryRelease(arg)) {
    
    //尝试释放锁 成功
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒后续节点
            return true;
        }
        return false;
    }

步骤一tryRelease(arg)

        protected final boolean tryRelease(int releases) {
    
    
            int c = getState() - releases;
            //判断当前线程是不是独占线程 不是就GG,不允许释放锁 抛出异常结束
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
    
     //判断锁是否释放干净了  这考虑了重入锁多次情况,有时候一次释放不干净
                free = true;
                setExclusiveOwnerThread(null);//清空独占线程
            }
            setState(c);
            return free;
        }

独占模式 下 同一时刻只有一个线程运行 并且设置成独占线程,在此基础上CAS操作state值,这是独占模式的核心设定。并且释放独占锁 必须当前线程持有独占锁的情况下进行,保证state值的原子性

步骤2 unparkSuccessor(h);唤醒后续节点,在我们lock系列方法里面的各种park挂起线程嘛,这里就是unpark了,唤醒后续节点线程让他动起来,然后在执行更新头节点等操作,到这里就闭环了

Condition newCondition()

这个比较多 单独介绍
Condition 介绍描述

共享模式

我们已ReentrantReadWriteLock.ReadLock做说明## void lock()

void lock()

读锁获取方法

	public void lock() {
    
    
    sync.acquireShared(1); // 调用 AQS 的共享模式获取锁
	}

AQS 的acquireShared()方法

	public final void acquireShared(int arg) {
    
    
        if (tryAcquireShared(arg) < 0)//tryAcquireShared()具体实现由 Sync 类提供
            doAcquireShared(arg);
    }

ReentrantReadWriteLock.Sync.tryAcquireShared方法,该方法上面已经讲过了,它会给出一个明确的结果尝试获取读锁或者失败,如果获取是否返回-1,则进入doAcquireShared方法,该方法是模板方法,入队 自旋判断 挂起 等待唤醒

void lockInterruptibly() throws InterruptedException

2.可中断读锁获取:lockInterruptibly()
该方法是实现Lock接口方法,核心在于可响应中断,即中断获取读锁的操作

        public void lockInterruptibly() throws InterruptedException {
    
    
            sync.acquireSharedInterruptibly(1);//调用 AQS 的共享中断模式获取锁
        }

acquireSharedInterruptibly的方法和上面AQS 的acquireShared()方法区别就在于响应中断

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    
    
         //判断是否有中断,有则响应中断抛出中断异常 方法就停止 即中断获取读锁的操作
        if (Thread.interrupted())throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

doAcquireSharedInterruptibly方法和上面的doAcquireShared区别在于中断抛出异常

boolean tryLock()

重写的Lock方法,尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,否则返回false

    public boolean tryLock() {
    
    
         return sync.tryReadLock();//具体实现由Sync提供
    }

sync.tryReadLock()

   final boolean tryReadLock() {
    
    
      //当前线程  	
      Thread current = Thread.currentThread();
      for (;;) {
    
    //自旋 直到成功或明确失败退出 这点和Sync里读锁的获取方法设计理念一致
        int c = getState();
        	//检查写锁状态 如果当前有写锁并且是被别的线程持有 直接失败退出
           if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
                 return false;

			//走到这里 意味着没有线程持有写锁 或者写锁被当前线程持有 后者情况 锁降级
              int r = sharedCount(c); 
              //获取读锁计数 判断是否超限 如果超直接抛出错误退出
              if (r == MAX_COUNT) throw new Error("Maximum lock count exceeded");
              // CAS 更新读锁计数,如果CAS失败,说明有线程竞争导致CAS失败:继续循环重试(自旋)
              if (compareAndSetState(c, c + SHARED_UNIT)) {
    
    
              	//CAS操作成功 就已经获取到读锁了 进行后续更新本地线程记录 
              	//先处理firstReader 判断是否首次获取读锁
                 if (r == 0) {
    
    
                 	//是首次获取读锁 设置当前线程为首次获取读锁线程
                     firstReader = current;
                     // 设置首次获取读锁计数值为1
                     firstReaderHoldCount = 1;
                 } 
                 // 如果r 不为0,那么判断 当前线程是否是首个读锁持有者
                 else if (firstReader == current) {
    
    
                 	// 是 重入 计数值+1
                     firstReaderHoldCount++;
                 } 
                 	// 既不是首次获取读锁 当前线程也不算首次获取读锁线程 则从缓存线程对象找
					else {
    
    
						// 获取缓存对象
                     	HoldCounter rh = cachedHoldCounter;
                     	// 判断缓存对象是否有效并且是当前线程的缓存
                    	 if (rh == null || rh.tid != getThreadId(current))
                     	//不是有效缓存或者不是当前线程的缓存 从ThreadLocal本地线程变量集合里拿
                        cachedHoldCounter = rh = readHolds.get();
                        
                        //缓存对象计数值是否归0了,0则重置ThreadLocal(缓存计数归0需要清理资源)
                    	else if (rh.count == 0)readHolds.set(rh);
                    	
                    	//存储线程本地重入次数+1
                        rh.count++;
                    }
                    return true;
                }
            }

总结:
①tryReadLock()方法和Sync类tryAcquireShared方法的前半段一致,只是没有兜底方法
tryReadLock()方法,非阻塞特性:如果写锁已经被其他线程占用,则不会阻塞当前线程,而是会立即返回flase,使用自旋方式主要在于CAS操作失败重试,存在多个线程竞争CAS操作更新state值,如果CAS操作失败则自旋重试, 这里自旋的次数取决于竞争线程的数量,理论上也很快就可以返回结果,如果能够获取则返回true,否则返回false
与lock方法区别在于 Lock会入队挂起线程 直到唤醒才会继续自旋执行 直到获取到锁,理论上Lock方法一定会获取到锁,只是时间长短问题,而tryLock不涉及到入队线程不存在挂起,如果CAS操作失败也会自旋 但取决于竞争线程数量,这个会很快的,理论上tryLock方法会立即返回结果值明确成功获取锁还是失败,tryLock方法不一定能获取到锁

boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

重写lock接口的方法,作用:超时的尝试获取锁

//首先该方法抛出`InterruptedException`,说明该方法可以响应中断
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    
    
            //调用Sync方法实现 参数1尝试获取锁数量,参数timeout指定超时时间 单位纳秒 
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }

sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));通过Sync继承AQS,是AQS模板方法实现

	//AQS模板方法
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    
     
        //抛出中断异常 说明可以响应中断异常
        if (Thread.interrupted())throw new InterruptedException();
        //调用两个方法Sync.tryAcquireShared 和 doAcquireSharedNanos(arg, nanosTimeout)
        return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
    }

使用Sync.tryAcquireShared 方法尝试快速获取锁,立即返回结果值,返回值1代表获取锁 返回-1代表获取锁失败,值得注意的是tryAcquireShared(arg)是非阻塞的 如果写锁被其他线程持有直接返回-1,后续如果CAS操作失败 则进入兜底方法重试直到成功返回1

当tryAcquireShared(arg) >= 0 返回false 意味着没有获取到锁 进入doAcquireSharedNanos方法,这个方法特点在于 1 可响应中断 2超时自动解挂,如果超过时间了 线程也会自动解除挂起,参与锁的竞争工作

void unlock()

释放锁

public void unlock() {
    
    
    sync.releaseShared(1); // 调用 AQS 的共享模式释放锁
}

releaseShared方法核心

    public final boolean releaseShared(int arg) {
    
    
        if (tryReleaseShared(arg)) {
    
     //调用Sync实现的读锁释放方法
            doReleaseShared();//如果释放锁成功后 调用doReleaseShared方法唤醒后续节点
            return true;
        }
        return false;
    }

doReleaseShared这里还是AQS方法了,做了幻想后续节点的操作

private void doReleaseShared() {
    
    
    for (;;) {
    
    
        Node h = head;
        if (h != null && h != tail) {
    
     //这说明队列中至少有一个有效节点
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
    
    
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // CAS失败,重试
                unparkSuccessor(h);     // 唤醒后继节点
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // CAS失败,重试
        }
        
        if (h == head)                   // head未变化,退出循环
            break;
    }
}

步骤说明
①自旋进入判断队列有效节点只是有一个,然后进入
②通过唤醒状态判断 头节点后有需要唤醒的线程,通过CAS状态更新为0,如果成功则进行唤醒后续节点操作,如果不成功则进行自旋重试,确保只有一个线程触发唤醒操作,避免其他线程重复唤醒
③ unparkSuccessor(h); 唤醒后续节点并更新头节点
④ 如果步骤②中头节点状态非唤醒状态,则进行尝试通过 CAS 将状态从 0 改为 PROPAGATE,失败:说明其他线程修改了状态,继续循环,成功则继续往下面走if判断头节点是否变化那去

这个方法到这里就要介绍下propagate状态了,在AQS中节点状态有 默认状态0,唤醒状态signal(-1),传播状态propagate(-3),另外还有取消状态1 和 条件状态-2,条件状态和条件队列有关,取消代表节点需要被清理,这里我们介绍前三种状态

唤醒状态signal,唤醒后续的节点,正常情况下同步队列中的状态是这个,当节点称为头节点后,要执行唤醒后续节点的操作,那么就会把节点状态从唤醒状态 -1 更新为0,如果头节点为0了,表示已经进行唤醒后续节点操作,在上面方法的case 1 里面体现了

            if (ws == Node.SIGNAL) {
    
    
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // CAS失败,重试
                unparkSuccessor(h);     // 唤醒后继节点
            } 

当 head 状态为 PROPAGATE 时,后续线程在获取资源时会 强制重试,确保释放信号被传播

传播状态propagate,上面方法还有个case 2 条件更新成传播节点,这个节点状态设计的巧妙

else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

如果线程进入发现头节点状态为0,就把头节点状态设置为 传播状态PROPAGATE(-3),什么情况下进来会发现头节点会变成0呢,是特意为并发情况考虑的

如果线程1 和线程2同时执行释放锁的操作,这种情况下只能有一个进行CAS操作成功唤醒后续节点,那另外的那个线程怎么办,总不能不管了吧,处理并发的实现方式如下

线程1执行doReleaseShared方法--》头节点SIGNAL更新为0--》触发唤醒后续节点unparkSuccessor
线程2执行doReleaseShared方法--》头节点已为0 更新成PROPAGATE --》判断发现头节点未变化 结束方法

这个时候线程1的unparkSuccessor方法通过唤醒线程LockSupport.unpark(s.thread);又回到了类似doAcquireShared的请求获取锁的挂起处parkAndCheckInterrupt())那里,解除线程挂起,然后继续执行doAcquireShared方法的自旋(这个线程从哪里挂起的从哪里恢复执行)

            for (;;) {
    
    
                final Node p = node.predecessor();
                if (p == head) {
    
    
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
    
    
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//unparkSuccessor恢复到这里开始执行
                    interrupted = true;
            }

然后再次进行自旋,又回到上面的请求获取锁,然后尝试获取锁呀,然后进入setHeadAndPropagate方法,这个方法除了设置新的头节点外,还做了是否触发doReleaseShared的操作

//参数propagate 是尝试获取锁tryAcquireShared(arg)的返回值,>0代表还有剩余资源可用
private void setHeadAndPropagate(Node node, int propagate) {
    
    
  // 旧的头节点
  Node h = head;
  // 将当前获取到锁的节点设置为头节点
  setHead(node);

  // 如果仍然有多的锁(propagate的值是nonfairTryAcquireShared()返回值)
  // 或者旧的头结点为空,或者头结点的 ws 小于0
  // 又或者新的头结点为空,或者新头结点的 ws 小于0,则唤醒后继节点
  if (propagate > 0 || h == null || h.waitStatus < 0 ||
      (h = head) == null || h.waitStatus < 0) {
    
    
      Node s = node.next;
      if (s == null || s.isShared())
          doReleaseShared();
  }
}

其中一个条件h.waitStatus < 0 ,旧的头节点状态是否小于0,上面我们说线程2把头节点设置成 传播状态-3,是满足小于0的,又触发了doReleaseShared()方法,是不是感觉又回来了

线程2再次进入doReleaseShared()方法,这个时候的头节点已经是线程1执行后更新头节点,这个时候根据新的头节点再次判断是否满足触发后续节点唤醒操作,如果触发唤醒解挂线程回到请求挂起处

传播状态propagate总结
1.生成背景:多线程并发下竞争头节点做唤醒后续节点操作失败的线程,更新头节点状态成传播状态
2.作用:在请求获取锁更新头节点方法,通过旧头节点状态判断 触发doReleaseShared,这里作用一加速传播:加速唤醒后继节点,在更新头节点时候就可以触发一次释放唤醒后续节点的操作方法doReleaseShared,毕竟,调用 doReleaseShared 方法越多、越早就越有可能更快的唤醒后继节点,作用二:强制传播,判断条条件里面 propagate > 0 || h.waitStatus < 0 或者关系,propagate 是尝试获取锁资源的返回值,说明即使没有资源了(propagate < =0),旧节点为传播状态这样也会触发释放方法doReleaseShared

细节注意哈,doReleaseShared方法只是唤醒后续节点,并不代表它就是执行成功了,从恢复解挂的线程unparkSuccessor回到doAcquireShared处,解挂还要自旋的 还在再判断是否有可用资源的,通过if (tryAcquireShared(arg) >= 0),不通过的还是再次被挂起的,propagate状态不是说一定能拿到锁的,只是给拿到锁这一过程 提提速

Condition newCondition()

条件实例,这个没有重写只有引用Lock接口

        public Condition newCondition() {
    
    
            throw new UnsupportedOperationException();
        }

总结

方法作用总结

lock方法 :优先考虑的是拿到锁,不可中断,方法内设置 记录中断标识 给上一层代码去处理,直到拿到锁结束
lockInterruptibly方法:优先考虑的是响应中断,获取锁的过程中如果有中断,即中断当前线程获取锁的过程,清除加入队列的节点,原先线程执行的继续执行
tryLock方法:快速尝试获取锁,会立即给出一个拿到锁没有拿到锁的 结果值
tryLock(long time, TimeUnit unit)方法:规定时间内快速尝试获取锁,并且可以响应中断,如果有中断或者规定时间内没有拿到锁,立即返回false,因时间单位很小,也属于立即给出是否拿到锁的结果值

unLock:释放锁 并唤醒后续节点,解挂后续节点线程,和Lock系列方法闭环了
Condition:将已经获取锁的线程(获取锁的线程就已经不在同步队列了),通过设定的执行条件是否满足决定是否暂停该线程的执行,通过await方法将其转移如 条件队列,直到被精准唤醒该线程,然后在将该线程从条件队列清除,在封装成节点从队尾加入同步队列再次竞争锁

共享模式和独占模式中头节点状态变化

独占模式 :刚称为头节点状态是 唤醒状态SIGNAL = -1,当头节点执行了唤醒后续节点操作 就更新为0

共享模式:刚称为头节点状态是 唤醒状态SIGNAL = -1当头节点执行了唤醒后续节点操作 就更新为0
但是共享模式是同一时间多个线程持有锁,所以有多线程并发执行释放锁的情况,这个时候操作的头节点是同一个,第一个执行更新头节点的变成0成功,第二个线程没竞争过就会将头节点状态再从默认状态0 更新为 传播状态propagate = -3,上面详细描述该状态作用

主要是独占模式 会设置独占线程 同一时间只有一个线程可以运行操作,不存在并发情况
而共享模式是存在并发执行的情况,所以单独出现了传播状态propagate 来处理这类情况