线程,同步器,锁

本文深入解析Java线程的六种状态及转换,详细阐述了锁接口lock的特性和使用方法,包括非阻塞获取锁、中断响应、超时获取锁等功能。通过分析AbstractQueuedSynchronizer(AQS)的实现原理,介绍了自定义同步组件的步骤,以及ReentrantLock、读写锁的公平与非公平获取策略。同时,文章探讨了LockSupport工具和Condition接口的使用,为Java并发编程提供了全面的理论基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

线程

状态名称说明
NEW初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE运行状态,Java线程将操作系统中的就绪和运行两种状态都笼统的称作为“运行中”
BLOCKED阻塞状态,表示线程阻塞于锁上
WAITING等待状态,表示线程进入等待状态,进入等待状态表示当前线程需要等待其他线程作出一些特定动作(通知或中断)
TIME_WAITING超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED终止状态,表示当前线程已经执行完毕

Java线程状态变迁

Java的锁

lock接口

lock接口提供了与synchronized关键字类似的同步功能,只是在使用时需要显式获取释放锁,索然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁释放与获取的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

特性描述
尝试非阻塞地获取锁当前线程会尝试获取锁,如果这一时刻没有被其他线程获取到,则成功获取并持有锁。
能被中断地获取锁与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
超时获取锁在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

队列同步器

队列同步器AbstractQueuedSynchronizer同步器,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

队列同步器的接口与示例

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

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

  • getState():获取当前同步状态。
  • setState(int new State):设置当前同步状态。
  • 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(int arg)方法
void acquireInterruptibly(int arg)与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回
boolean tryAcquireNanos(int arg, long nanosTimeout)在acquireInterruptibly(int arg)基础上增加了超时限制,如果当前线程在超时时间内没有获取同步状态,那么将会返回false,如果获取到了返回true
void acquireShared(int arg)共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
void acquireSharedInterruptibly(int arg)与acquireShared(int arg)相同,该方法响应中断
boolean tryAcquireSharedNanos(int arg, long nanosTimeout)在acquireSharedInterruptibly(int arg)基础上增加了超时限制
boolean release(int arg) 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
boolean releaseShared(int arg)共享式的释放同步状态
Collection<Thread> getQueuedThreads() 获取等待在同步队列上的线程集合

同步器提供的模版方法基本上分为3类:

  1. 独占式获取与释放同步状态
  2. 共享式获取与释放同步状态
  3. 查询同步队列中的等待线程情况

独占式就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,其他的线程代能够获取锁。

public class Mutex implements Lock {

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        //当状态为0的时候,获取锁
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //释放锁,将状态设置为0
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected int tryAcquireShared(int arg) {
            return super.tryAcquireShared(arg);
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            return super.tryReleaseShared(arg);
        }

        @Override
        //是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //返回一个condition 每一个condition都包含一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();

    public boolean isLocked(){
        return sync.isHeldExclusively();
    }

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

独占锁Mutex是一个自定义同步组件,它在同一时刻只允许一个线程占有锁。定义了静态内部类,该内部类继承了同步器,并实现了独占式获取和释放同步状态,在tryAcquire方法中, 经过CAS设置成功(同步状态设置为1),则代表了获取了同步状态,而在tryRelease方法中将同步状态重置为0。当前线程获取同步状态失败后会被加入到同步队列中等待。

队列同步器的实现分析

1.同步队列

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

属性类型与名称描述
int waitStatus

等待状态

  1. 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引用即可

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

调用同步器的acquire(int arg)方法获取不同状态,如果线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquire(int arg)方法主要完成了同步状态获取、节点构造、加入队列以及在同步队列中自旋等待的工作,其主要逻辑是:

  • 调用自定义同步器实现的tryAcquire(int acquires)方法,获取同步状态,
  • 如果失败,构造同步节点(独占式Node.EXCLUSIVE,同一时刻只有一个线程成功获取同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部
  • 最后调用acquireQueued(Node node, int arg)方法,使该节点以“死循环”的方式获取同步状态,获取不到就阻塞节点中的线程,阻塞的线程唤醒依靠前驱节点的出队或阻塞线程被中断来实现。
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

addWaiter(Node node)方法通过compareAndSetTail(pred, node)来保证节点安全的添加,如果用linkedList有可能在并发情况下造成混乱。

addWaiter(Node node)中的enq(final Node node) 方法中,同步器通过“死循环”CAS的方式将节点添加到队尾

addWaiter(Node node)中把当前线程整合成和node节点属性创建一个Node对象,然后将此对象的prev(上一个)属性设置值,值是全局的tail(尾部)node对象,然后CAS把当前对象的node设置成全局的tail,设置成功后,把之前全局tail的next属性指向当前线程node。

通过addWaiter已经把当前线程的NODE放到了对了的最后

end(final Node node)

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

判断全局tail是不是null,

tail是null,那么表示队列里面没有线程,并且当前线程还没获取到锁,例如2个线程并发第一个线程获取到了锁,那么第二个线程进来的时候这里就是null的,那么就创建了队列,因为只有一个元素,那么就全局tail=head。

tail不是null,表示的是队列之前就有其他线程在等待了,那么设置形式就和addWaiter的一样,这里和addWaiter一样的原因是并发下有可能多个线程进入的时候队列为空,那么就都进入了enq里面去死循环修改,那么就有可能在CAS的时候有一个成功,后面并发的线程就不为空了,有点类似double check。

acquireQueued(final Node node, int arg)方法

  1.  

acquireQueued(final Node node, int arg)方法中,当前线程在“死循环”尝试获取同步状态,只有前驱节点是头节点才能尝试获取同步状态,原因有两个

  1. 头节点是成功获取到同步状态的节点,只有头节点才能唤醒后继节点,后继节点唤醒后会检查自己的前驱是不是头节点
  2. 维护同步队列的FIFO原则

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

释放同步状态,调用同步器的release(int arg)方法。该方法释放同步状态后,会唤醒其后继节点。

总结:获取同步状态时,同步器维护同步队列,获取失败的线程都会被加入到队列的尾部进行自旋;移出队列的条件式前驱节点为头节点且成功获取同步状态。释放同步状态,同步器调用release(int arg)方法释放,然后唤醒后继节点。

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

允许同一时刻有多个线程获取同步状态。

调用同步器的acquireShared(int arg) 方法可以共享式获取同步状态。

在共享式获取的自旋过程中,成功获取到同步状态并退出的自旋条件就是 tryAcquireShared(int arg)方法返回值大于等于0,在doAcquireShared(int arg) 方法自旋的过程中,如果当前节点是的前驱为头节点时,尝试调用doAcquireShared(int arg) 获取同步状态,如果大于等于0了,就从自旋中退出。

共享式释放同步状态,通过调用releaseShared(int arg)方法释放同步状态。将会唤醒后续处于等待状态的节点。

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

和独占式获取同步状态区别在于获取到同步状态的逻辑,独占式未获取到同步状态时,会使当前线程一直等待,而独占式超时获取会使当前线程等待nanosTimeout纳秒,如果线程在纳秒内没获取到状态,就从等待中返回。

5.自定义同步组件

 

 

 

 

重入锁

重入锁 ReentrantLock, 就是支持重进入的锁,表示该锁能够支持一个线程对资源的重复加锁,还支持获取锁时的公平和非公平性选择。

1. 实现重入

重进入是指任意线程在获取锁之后能够再次获取该锁而不会被锁阻塞,实现该特性需要解决下面两个问题:

  1. 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
  2. 锁的最终释放。线程重复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()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

该方法增加了再次获取同步状态的逻辑,判断当前线程是否为获取锁的线程,如果是就把计数+1,并返回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方法必须返回false,而只有同步状态值等于0,才返回true,如果代码中获取了而有一次不释放,那么就会死锁。

2. 公平与非公平获取锁的区别

ReentrantLock 的公平和非公平,判断的方式就在公平锁内部类FairSync的tryAcquire(int acquires)实现上,比非公平锁的方法多了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());
    }

看return,首先 h!=t,表明了队列中有多个node,如果h=t了,那说明队列中只有当前线程自己,并且h.next()==null ,说明h是尾节点。 或者 s的线程,不是当前线程。

3. 读写锁

锁基本上都是排他锁,这些锁在同一时刻只允许一个线程进行访问。而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多写少的情况下,读写锁能够提供比排它锁更好的并发和吞吐量,

ReentrantReadWriteLock

特性说明
公平性选择支持非公平(默认)和公平的获取锁方式,吞吐量还是非公平优于公平
重进入该锁支持重进入,以读写线程为例;读线程在获取了读锁之后,能够再次获取读锁,而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁
锁降级遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级为读锁

3.1 读写锁接口

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

方法名称描述
int getReadLockCount()返回的当前读锁被获取的次数,该次数不等于获取读锁的线程书,例如,仅一个线程,它连续获取(重进入)了n次读锁,那么占据读锁的线程数是1,但该方法返回n
int getReadHoldCount()返回当前线程获取读锁的次数,该方法使用ThreadLocal保存当前线程获取的次数
boolean isWriteLocked()判断写锁是否被获取
int getWriteHoldCount()返回当前写锁被获取的次数
public class Cache {
    private static Map<String ,Object> map = Maps.newHashMap();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w =rwl.writeLock();

    public static final Object get(String key){
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    public static final Object put(String key, Object object){
        w.lock();
        try {
            return map.put(key, object);
        } finally {
            w.unlock();
        }
    }

    public static final void clear(){
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}

用非安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁老保证cache是线程安全的。在读操作get的时候,需要获取读锁,使得并发访问时不会被阻塞,写操作put和clear的时候,需要获取写锁,获取写锁后,其他线程的读锁和写锁均被阻塞,只有写锁被释放后,其他读写操作才能继续。

3.2 读写锁的实现

读写锁同样依赖自定义同步器来实现,而读写状态就是其同步器的同步状态。ReentrantLock中自定义同步去的同步状态表示锁被一个线程重复获取的次数,而读写锁需要在同步器上维护的是(一个整型变量),多个读线程和一个写线程的状态,

在一个整型变量上维护多种状态,需要按位切割使用这个变量,高16位表示读,低16位表示写.

3.3 写锁的获取与释放

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

 protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                 // 存在读锁或者当前线程不是已经获取写锁的线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

c是写锁,w是读锁,如果当前c不是0,那么就是被线程占用了,进入if,判断读锁是否存在的判断。如果存在读锁,那么w!=0,则写锁不能被获取,原因在于,读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁获取,那么运行的其他读线程就无法感知到当前写线程的操作。因此,只有等待其他线程都释放了读锁,写锁才能被当前线程获取,而写锁获取之后,其他读写线程的后续访问均被阻塞。

写锁的释放与ReentrantLock的释放基本一样,每次释放减少写状态,当写状态为0表示写锁已经被释放了,其他等待读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。

protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

首先判断了当前线程是否是占用锁的线程。

如果是的话就释放一次锁状态,判断锁状态是不是释放成0,等于0的话,重置当前获得锁的线程为空,然后更新state=0,表示其他线程可以继续访问读写锁

3.4 读锁的获取与释放 

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取。

如果在没有其他写线程访问(或者写状态为0)时,读锁总会被成功地获取,而所做的也只是增加读状态。

如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态。

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);
        }
    }

在tryAcquireShared(arg)中如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入队列等待。如果当前线程获取了写锁或者写锁未被获取,则当前线程依靠CAS增加读状态,成功获取读锁。

读锁的每次释放均减少读状态。

3.5锁降级

锁降级指的是写锁降级成为读锁

如果当前线程拥有写锁,然后释放写锁,之后获取读锁,这种分段完成的过程不是锁降级。

锁降级是把持住当前的写锁,再获取到读锁,锁后释放先前拥有的写锁的过程。

4 LockSupport工具

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

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

5. Condition接口

对比项Object Monitor MethodsCondition
前置条件获取对象的锁

调用Lock.lock()获取锁

调用Lock.newCondition()获取Condition对象

调用方式直接调用,如object.wait()直接调用,如condition.await()
等到队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入等待状态到将来的某个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

5.1 Condition实现分析

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,实现了Condition接口。

5.1.1等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程会释放锁。构造成节点加入等待并进入等待状态,节点的定义复用了同步器中的节点定义。

等待队列的基本结构

Condition拥有首尾节点的引用,新增节点只需要将原有的尾节点nextWaiter指向他,并且更新尾节点即可,并没有CAS保证更新,原因是调用await方法的线程必定是获取了锁的线程,也就是这个过程由锁来保证的线程安全。

Condition的实现是同步器的内部类,因此每个Condition实例都能够访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。

5.1.2 等待

调用了await方法,会使当前线程进入等待队列并释放锁,同时线程状态变为等待,当从await返回时,当前线程一定是获取了Condition相关联的锁。

调用了await方法,由于同步器的关系,队列中的首节点获取到了锁,然后把这个获取了锁的节点转移到了Condition等待队列中去。释放同步状态,唤醒同步队列中的后继节点,

5.1.3 通知

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

通过调用同步器的enq方法,将等待队列中的头节点线程安全的移动到同步队列,当节点移动到同步队列后,当前线程再使用LockSupport唤醒该节点的线程。

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

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

Condition的singalAll方法,相当于对等待队列中的每个节点均执行一次singal方法,就是把所有等待队列中的线程都移动到同步队列,并唤醒每个节点的线程。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值