Java并发—锁

21 篇文章 6 订阅
5 篇文章 0 订阅

Java中的锁

1.Lock接口

锁是用来控制多个线程访问共享资源的方式, 一般来说, 一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源, 比如读写锁). 在Lock接口出现之前, Java程序是靠synchronized关键字来实现锁功能的, 它提供了和synchronized关键字类似的同步功能, 只是在使用时需要显示的获取和释放锁. 虽然它缺少了隐式获取释放锁的便捷性, 但却拥有了锁获取与释放的可操作性, 可中断性的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性

2.队列同步器(AQS)

队列同步器AbstractQueueSynchronizer, 是用来构建锁或者其他同步组件的基础框架, 它使用了一个int成员变量表示同步状态, 通过内置的FIFO队列来完成资源获取线程的排队工作

同步器的主要使用方法是继承, 子类通过继承同步器并实现它的抽象方法来管理同步状态, 在抽象方法的实现过程中免不了要对同步状态进行更改, 这时就需要使用同步器的3个方法(getState(), setState(int newState), 和 compareAndSetState(int expect, int update))来进行操作, 因为它们能够保证状态的改变是安全的.

同步器是实现锁(也可以是任意同步组件)的关键, 在锁的实现中聚合同步器, 利用同步器实现锁的语义. 可以这样理解二者之间的关系 : 锁是面向使用者的; 同步器是面向锁的实现者, 简化了锁 的实现方式, 屏蔽了同步状态管理, 现成的排队, 等待与唤醒等底层操作

1>AQS的接口与示例

同步器的设计是基于模板方法模式的, 也就是说, 使用者需要继承同步器并重写指定的方法, 随后将同步器组合在自定义同步组件的实现中, 并调用同步器提供的模板方法, 而这些模板方法将会调用使用者重写的方法

重写同步器指定的方法时, 需要使用同步器提供的如下3个方法来访问或修改同步状态

  • getState() : 获取当前同步状态
  • setState() : 设置当前同步状态
  • compareAndSetState(int expect, int update) : 使用CAS设置当前状态, 该方法能够保证状态设置的原子性
方法名称描述
protected boolean tryAcquire(int arg)独占式获取同步状态, 实现该方法需要查询当前状态并判断同步状态是否符合预期, 然后再进行CAS设置同步状态
protected boolean tryRelease(int arg)独占式释放同步状态, 等待获取同步状态的线程将有机会获取同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态, 返回大于等于0的值, 表示获取成功 , 反之, 获取失败
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用, 一般该方法表示是否被当前线程所独占

实现自定义同步组件时, 将会调用同步器提供的末班方法

方法名称描述
void acquire(int arg)独占式获取同步状态, 如果当前线程获取同步状态成功, 则由该方法返回, 否则将会进入同步队列等待. 该方法将会调用重写的tryAcquire()方法
void acquireInterruptibly(int arg)与acquire(int arg)相同, 但该方法响应中断, 当前线程为获取到同步状态而进入同步队列时, 如果当前线程被中断, 则会抛出InterruptedExecption并返回
boolean tryAcquireNanos(int arg, long nanos)在acquireInterruptibly(int arg) 的基础上增加了超时限制, 如果当前方法在超时时间内没有获取到同步锁, 则返回false, 否则返回true
void acquireShared(int arg)共享式的获取同步状态, 如果当前线程未获取到同步状态, 将会进入同步队列等待, 与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg)与acquireSharedy(int arg)相同 , 该方法响应中断
boolean tryAcquireSharedNanos(int arg, long nanos)在acquireSharedInterruptibly(int arg)基础上增加了超时限制
boolean release(int arg)独占式的释放同步状态, 该方法会 在释放同步状态之后, 将同步队列中第一个节点包含的线程唤醒
boolean releaseShared(int arg)共享式的释放同步状态
Collection< Thread> getQueueThreads()获取等待在同步状态的线程集合

同步器提供的模板方法基本上分为3类, 独占式获取与释放同步状态, 共享式获取与释放同步状态和查询同步队列中的等待线程情况.

2>AQS的实现分析

**a.同步队列 : **

同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理, 当前线程获取同步状态失败时, 同步器会将当前线程以及等待状态等信息构造成为一个节点(Node), 并将其加入同步队列, 同时会阻塞当前线程, 当同步状态释放时, 会把首节点中的线程唤醒, 使其再次尝试获取同步状态

同步队列中的节点(Node)用来保存获取同步状态失败的线程引用, 等待状态以及前驱和后继节点

属性类型与名称描述
int waitStatus1.CANCELLED,值为1, 由于同步队列中等待的线程等待超时或者被中断, 需要从同步队列中取消等待, 节点进入该状态将不会变化 ; 2.SIGNAL, 值为-1, 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或被取消, 将会通知后继节点, 使后集节点的线程得以运行; 3.CONDITION, 值为-2, 节点在等待队列中, 节点线程登载在Condition上, 当其他线程对Condition调用了signal()方法后, 该节点将会从等待队列中移动到同步队列中, 加入到对同步状态的获取中 4.PROPAGATE , 值为-3, 表示下一次共享式同步状态获取将无条件地被传播下去; 5.INITIAL, 值为0, 初始状态
Node prev前驱节点, 当前节点加入同步队列时设置
Node next后继节点
Node nextWaiter等待队列中的后继节点, 如果当前节点是共享的, 那么这个字段将是一个SHARED常量, 也就是说节点类型(共享和独占)和等待队列中的后继节点共用一个字段
Thread thread获取同步状态的线程

节点是构成同步队列的接触, 同步器拥有首节点(head)和尾节点(tail), 没有成功获取同步状态的线程会为节点加入该队列的尾部, 同步队列的基本结构如图:

在这里插入图片描述

同步器包含了两个节点类型的引用, 一个指向头结点, 而另一个指向尾节点. 一个线程如果没有获取到同步状态, 要放入同步队列时, 需要保证线程安全, 因此同步器提供了一个基于CAS的设置尾节点的方法 : compareAndSetTail(Node expect, Node update), 它需要传递当前线程"认为"的尾节点和当前节点, 只有设置成功后, 当前节点才正式 与之前的节点建立关联

同步队列遵循FIFO, 首节点是获取同步状态成功的节点, 首节点的线程在释放同步状态的时, 会唤醒其后继节点 , 而后继节点将会在获取同步状态成功时将自己设置为首节点.

设置首节点是通过获取同步状态成功的线程完成的, 由于只有一个线程能成功获取到同步状态, 因此设置头结点的方法不需要使用CAS来保证, 只需要将首节点设置为原首节点的后继节点并断开首节点的next引用即可

b.独占式同步状态获取与释放

通过调用同步器的acquire(int arg)方法可以获取同步状态, 该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中, 后续对线程进行中断操作时, 线程不会从同步队列中移出

	//同步器的acquire方法
	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

上述代码主要完成了同步状态获取, 节点构造, 加入同步队列以及在同步队列中自旋等待的相关工作, 其逻辑是 : 先调用自定义同步器实现的tryAcquire(int arg)方法, 改方法保证线程安全的获取同步状态, 如果同步状态获取失败, 则构造节点(独占式Node.EXCLUSIVE, 同一时刻只能有一个线程成功获取同步状态) 并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部, 最后调用acquireQueued(Node node, int arg)方法, 使得该节点以"死循环"的方式获取同步状态. 如果获取不到则阻塞节点中的线程, 而被阻塞线程的唤醒主要依靠前驱节点的出队或者阻塞线程被中断来实现

	private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 尝试在尾部添加
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {//CAS更新尾部节点
                pred.next = node;
                return node;
            }
        }
        //队列如果没有初始化,
        enq(node);
        return node;
    }
	private Node enq(final Node node) {
        for (;;) {//自旋进行CAS操作, 一直到更新成功
            Node t = tail;
            if (t == null) { // 要先进行初始化
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

节点进入同步队列后, 就进入了一个自旋的过程, 每个节点都在自省的观察, 当条件满足, 获取到了同步状态, 就可以从这个自旋中退出, 否则依然留在这个自旋中, 并会阻塞节点的线程

	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)
                cancelAcquire(node);
        }
    }

在acquireQueued(final Node node, int arg)方法中, 当前线程在"死循环"中尝试获取同步状态, 而只有前去节点是头结点才能够尝试获取同步状态, 这是因为 :

  • 头结点是成功获取到同步状态的节点, 而头结点的线程释放了同步状态之后, 将会唤醒其后继节点, 后集节点的线程被唤醒后需要检查自己的前驱节点是否是头结点
  • 维护同步队列的FIFO原则

独占式同步状态获取流程, 也就是acquire(int arg)方法调用流程, 如图所示

在这里插入图片描述

前驱节点为头结点且能够获取同步状态的判断条件和线程进入等待状态是获取同步状态的自旋过程. 当同步状态获取成功后, 当线程从acquire(int arg)方法返回, 如果对于锁这种并发组件而言, 代表着当前线程获得了锁

当前线程获取同步状态并执行了相应逻辑之后, 就需要释放同步状态, 使得后续节点能够继续获取同步状态, 通过调用同步器的release(int arg)方法可以释放同步状态, 该方法在释放了同步状态之后, 会唤醒其后继节点(从而使后继节点重新尝试获取同步状态)

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

该方法执行时, 会唤醒头结点的后继节点线程, unparkSuccessor(Node node)方法使用LockSupport来唤醒处于等待状态的线程

总结 : 在获取同步状态时, 同步器维护一个同步队列, 获取状态失败的线程都会被加入到队列中并在队列中进行自旋; 移除队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态. 在释放同步状态时, 同步器调用tryRelease(int arg)方法释放同步状态, 然后唤醒头结点的后继节点

c.共享式同步状态获取与释放

共享式获取与独占式最主要的区别在于同一时刻能否有多个线程同步获取到同步状态.

//同步器的acquireShared和doAcquireShared方法
	public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

	private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            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())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireShared(int arg)方法中, 同步器调用tryAcquireShared(int arg)方法尝试获取同步状态, tryAcquireShared(int arg)方法返回值为int类型, 当返回值大于等于0时, 表示能够获取到同步状态, 因此在共享式获取的自旋过程中, 成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0. 可以看到, 在都AcquireShared(int arg)方法的自旋中, 如果当前节点的前驱为头结点时, 尝试获取同步状态, 如果返回值大于等于0, 表示该次获取同步状态成功并从自旋过程中退出

调用releaseShared(int arg)方法可以释放同步状态

	public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

该方法在释放同步状态后, 将会唤醒后面处于等待状态的节点.对于能够支持多个线程同时访问的并发组件, 它和独占式主要区别在于tryReleaseShared(int arg)方法必须确保同步状态(或者资源数)线程安全释放, 一般是通过循环和CAS来保证的. 因为释放同步状态的操作会同时来自多个 线程

d.独占式超时获取同步状态

通过调用同步器的doAcquireNanos(int arg, long nanosTimeout)方法可以超过获取同步状态, 即在指定时间段内获取同步状态, 如果获取到同步状态则返回true, 否则返回false.

该方法在自旋过程中, 当节点的前驱节点为头节点时尝试获取同步状态, 如果获取成功则从该方法返回, 这个过程和独占式同步获取的过程类似, 但是在同步状态获取失败的处理上有所不同. 如果当前线程获取失败, 则哦按段是否超时, 如果没有超时, 重新计算超时间隔, 然后使线程等待.

过程如下图所示

在这里插入图片描述

3.重入锁(ReentrantLock)

重入锁(ReentrantLock), 顾名思义就是支持重进入的锁, 它表示能够支持一个线程对资源的重复加锁, 除此之外, 该锁还支持获取所的公平性和非公平性选择

ReentrantLock虽然没能像synchronized关键字一样隐式的重进入, 但是在调用lock()方法时, 已经获取到锁的线程, 能够再次调用lock方法获取锁而不会被阻塞

公平性 : 如果在绝对时间上, 先对锁进行获取的请求一定先被满足, 那么这个锁就是公平的, 反之是不公平的

1>实现重进入

为了实现重进入, 要实现下面两个问题 :

  • 线程再次获取锁 : 锁需要去识别获取锁的线程是否为当前占据锁的线程, 如果是, 则再次获取成功
  • 锁的最终释放 : 线程重复n次获取了锁, 随后在第n次释放该锁之后, 其他线程能获取到该锁. 锁的最终释放要求锁对于获取进行计数自增, 计数表示当前锁被重复获取的次数, 而锁被释放时, 计数自减, 当计数等于0时表示锁已经成功释放

ReentrantLock是通过组合自定义同步器来实现锁的获取与释放, 以非公平性的为例

	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()) {
                //当前线程就是占据的线程, state值增加
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

该方法增加了再次获取同步状态的处理逻辑 : 通过判断当前线程是否为获取锁的线程来决定获取操作是否成功, 如果是获取锁的线程再次请求, 则将同步状态值进行增加并返回true, 表示获取同步状态成功

成功获取所的线程再次获取锁, 只是增加了同步状态值, 这就要求ReentrantLock在释放同步状态时减少同步状态值

	protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

如果该锁被获取了n次, 那么前(n-1)次tryRelease(int releases)方法必须返回false, 而只有同步状态完全释放了, 才能返回true. 当同步状态为0时, 将占有线程设置为null, 并返回true, 表示释放成功

2>公平与非公平锁的区别

对于非公平锁, 只要CAS设置同步状态成功, 则表示当前线程获取了锁, 而公平锁则不同

	protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

该方法与nonfairTryAcquire(int acquires)比较, 唯一不同的位置为判断条件多了hasQueuedPredecessors()方法, 即加入了同步队列中当前节点是否有前驱节点的判断, 如果该方法返回true, 则表示该线程比当前线程更早地请求获取锁, 因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁

对于公平锁, 每次都是从同步队列中的第一个节点获取到锁, 而非公平锁会出现一个线程连续获取锁, 这是因为当一个线程请求锁时, 只要获取了同步状态即成功获取所. 所以刚释放锁的线程再次获取锁的几率很大, 使得其他线程只能在同步队列中等待

非公平锁可能使线程"饿死", 但其又被设定成默认的实现, 因为公平锁虽然保证了锁的获取按照FIFO原则, 但代价是进行大量的线程切换. 非公平锁虽然可能造成线程"饥饿", 但极少的线程切换, 保证了其更大的吞吐量

4.读写锁

之前提到的锁基本都是排他锁, 这些锁在同一时刻只允许一个线程进行访问, 而读写锁在同一时刻可以允许多个读线程访问, 但是在写线程访问时, 所有的读线程和写线程均被阻塞. 读写锁维护了一对锁, 一个读锁和一个写锁, 通过分离读锁和写锁, 使得并发性相比一般的排他锁有了很大的提升

一般情况下, 读写锁的性能都会比排他锁好, 因为大多数场景是读多于写的,这时候, 读写锁可以提供更好的并发性和吞吐量, Java并发包提供读写锁的实现是ReentrantReadWriteLock

1>读写锁的接口与示例

ReadWriteLock仅定义了获取读锁和写锁的两个方法, 即readLock()方法和WriteLock()方法, 而其实现, ReentrantReadWriteLock, 除了接口方法之外, 还提供了一些便于外界监控其内部工作状态的方法

方法名称描述
int getReadLockCount()返回当前读锁被获取的次数.该次数不等于获取读锁的线程数. 例如一个线程重进入了n次读锁,那么该方法返回n
int getReadHoldCount()返回当前线程获取读锁的次数, 使用ThreadLocal保存当前线程获取的次数
boolean isWriteLocked()判断写锁是否被获取
int getWriteHoldCount返回当前写锁被获取的次数
2>读写锁的实现分析
  • 1.读写状态的设计

    读写锁同样依赖自定义同步器来实现同步功能, 而读写状态就是其同步器的同步状态. 在ReentrantLock中, 同步状态表示锁被一个线程重复获取的次数, 而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和写线程的状态, 使得该状态的设计成为读写锁实现的关键

    读写锁将变量分成了两个部分, 高16位表示读, 低16位表示写状态

    读写锁通过位运算来确定读和写的各自状态, 假设当前同步状态为S, 则写状态等于S & 0x0000FFFF, 读状态等于S >>> 16

  • 2.写锁的获取与释放

    写锁是一个支持重进入的排他锁. 如果当前线程已经获取了写锁, 则增加写状态. 若当前线程在获取写锁时, 读锁已被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态

    写锁的释放与ReentrantLock的释放过程基本类似, 每次释放均减少写状态, 当写状态为0时表示写锁已被释放, 从而等待的读写线程能够继续访问读写锁

  • 3.读锁的获取与释放

    读锁是一个支持重进入的共享锁, 它能够被多个线程同时获取, 在没有其他写线程访问时, 读锁总会成功的获取, 而所做的也只是增加读状态, 如果当前线程获得了读锁, 则增加读状态(依靠CAS安全增加读状态).读锁的每次释放也是线程安全的, 均减少读状态

  • 4.锁降级

    锁降级指的是写锁降级成读锁, 如果当前线程拥有写锁, 然后将其释放, 最后再获取读锁, 这种分段完成的过程不能称之为锁降级. 锁降级是指把持住当前的写锁, 再获取到读锁, 之后释放写锁的过程

5.LockSupport工具

LockSupport定义了一组以park开头的方法来阻塞当前线程, 以及unpark(Thread thread)方法来唤醒一个被阻塞的线程

方法名称描述
void park()阻塞当前线程, 如果调用unpark(Thread thread)方法或者当前线程被中断, 才能从park()方法返回
void parkNanos(long nanos)阻塞当前线程, 最长不超过nanos纳秒, 返回条件在park()的基础上加上了超时返回
void parkUntil(long deadline)阻塞当前线程, 知道deadline时间
void unpark(Thread thread)唤醒处于阻塞状态的thread

6.Condition接口

任意一个Java对象, 都有一组监视器方法(定义在java.lang.Object上), 主要包括wait(), wait(long timeout), notify(), notifyAll()方法, 这些方法与synchronized同步关键字配合, 可以实现等待/通知模式. Condition接口也提供了类似Object的监视器方法, 与Lock配合可以实现等待/通知模式, 但是这两者在使用方式以及功能特性上还是有差别的

当调用await()方法后, 当前线程会释放锁并在此等待, 而其他线程调用Condition对象的signal方法, 通知当前线程后, 当前线程才从await()方法返回. 并且已经在返回前获取了锁

获取一个Condition必须通过Lock的newCondition()方法.

Condition的实现分析

ConditionObject是同步器AQS的内部类, 因为Condition的操作需要获取到相关联的锁

  • 1.等待队列

    等待队列是一个FIFO的队列, 在队列的每个节点都包括了一个线程引用, 该线程就是在Condition对象上等待的线程. 如果一个线程调用了Condition.await()方法, 那么该线程会释放锁, 构造成节点加入等待队列, 并 进入等待状态. 其实同步队列和等待队列猴子那个的节点类型都是同步器的静态内部类Node

    一个Condition包含一个等待队列, Condition拥有首节点(firstWaiter)和尾节点(lastWaiter). 当线程调用Condition.await()方法, 将会以当前线程构造节点并将节点从尾部加入等待队列, 如图所示

在这里插入图片描述

在Object的监视器模型上, 一个对象拥有一个同步队列和等待队列. 而并发包中的Lock(同步器)拥有一个同步队列和多个等待队列

  • 2.等待

    调用Condition的await()方法. 会使当前线程进入等待队列并释放锁, 同时线程状态变为等待状态. 当从await()方法返回时, 当前线程一定获取了Condition相关联的锁. 从队列的角度来看, 调用await()方法时, 相当于同步队列的首节点(获取了锁的节点), 移动到Condition的等待队列中

    调用该方法的线程成功获取了锁, 也就是同步队列的首节点, 该方法会将当前线程构造成节点并加入等待队列中, 然后释放同步状态, 唤醒同步队列的后继节点, 然后当前线程会进入等待状态

    当等待队列的节点被唤醒, 则唤醒节点的线程开始尝试获取同步状态. 如果不是通过其他线程调用Condition.signal()方法唤醒, 而是对等待线程进行中断, 则会抛出InterruptedException

  • 3.通知

    调用Condition的signal()方法, 将会唤醒在等待队列中等待时间最长的节点(首节点), 在唤醒节点之前, 会将节点移到同步队列中.

    调用该方法的前置条件是当前线程获取了锁,接着获取等待队列的首节点, 将其移动到同步队列并使用LockSupport唤醒节点中的线程

    被唤醒后的线程, 将从await()方法的while循环中退出, 进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中

    成功获取同步状态之后, 被唤醒的线程将从先前调用的await()方法返回, 此时线程已经成功的获取了锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值