学习《java并发编程的艺术》Chapter5

AbstractQueuedSynchronizer(AQS,抽象队列同步器):

    同步队列:

        同步器是依赖内部的同步队列(一个FIFO的双向队列)来完成同步状态的管理。在该同步器类的成员变量中,包含了两个节点类型的引用:头节点和尾节点,通过设置各个节点的前后关系形成的双向FIFO队列的数据结构。当线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点来将其加入同步队列的尾部,同时会阻塞当前线程,当同步状态释放时,会把首节点的线程唤醒,使其再次尝试获取同步状态。因为插入尾部存在并发的可能性,所以同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail;而设置头部是不存在竞争关系的,只会由获取到锁的线程解锁时来操作。

   独占式同步锁获取与释放:

        1.调用同步器的acquire方法尝试获取同步状态。由源码可以看到,acquire方法体内只有一个判断,当判断通过时执行selfInterrupt()方法,也就是中断当前线程,说明if判断不成立时,才能获取到锁。来看看if内是做了什么判断。

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

        先是tryAcquire方法。在AQS类中,该方法是直接抛出异常的,也就是说,这个方法是要交给继承了AQS的子类是自己实现的。根据注释,该方法旨在让当前线程尝试去获取锁,在获取到锁后将AQS的volatile变量state的值做赋值操作,成功返回true,否则返回false。

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

        如果获取失败的话,!tryAcquire(arg)的值为true,进入acquireQueued方法的判断。该方法要先执行addWaiter方法,addWaiter方法就是将当前线程包装成一个Node对象,然后通过对队列尾节点是否为空的判断来决定,是直接将当前线程包装的Node对象放到队列的尾部,还是调用enq方法先对队列进行一些初始化操作再将其放至尾部,但不论是哪种方式,都是通过CAS的方式设置。当addWaiter方法执行完,获取锁失败的线程就被包装成Node对象放到了队列的尾部,然后进入一个自旋的过程:每个节点都在不断循环的判断自己的前一个节点是不是头节点,如果是头节点的话,再不断执行tryAcquire方法去尝试获取锁,直至获取成功,获取成功后,将自己设置为头节点,并返回false。至于finally部分的话,个人猜测是,在获取锁成功后,在执行setHead(node)或p.next = null时出现异常,则取消这个节点获取锁的操作。

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

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

        当线程获取到同步状态并执行完相应逻辑后,看需要调用release方法释放同步状态,使得后续节点能够继续获取同步状态。执行release方法先是对tryRelease方法的返回值进行判断,可以看到tryRelease方法在AQS中是直接抛出异常的,也就是需要继承了AQS的子类去自己实现的,注释大致是说,试图以独占的方式设置state变量的值来表明已释放的状态,释放成功返回true,否则为false。释放成功后,如果头节点不为空且不是无任何状态,也就是队列中还有节点在嗷嗷待哺的话,执行unparkSuccessor方法唤醒后继结点。unparkSuccessor方法使用了LockSupport工具类,该工具类主要是通过调用unsafe类来控制线程的阻塞唤醒等。

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

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

   共享式同步锁获取与释放:

        共享锁和独占锁的获取最主要的区别在于,同一时刻能否有多个线程同时获取到同步状态。独占锁通过调用acquire方法尝试获取同步状态,共享锁通过acquireShared方法。在AQS类中,tryAcquireShared方法是直接抛出异常的,也就是需要继承AQS的子类去实现state变量能否被当前线程共享的判断,如果能够则令该方法返回一个不小于0的值即可。当返回的值小于0时,则将当前线程包装成一个worker对象放入队列中,并自旋的判断自己的前驱节点是否为头结点,如果是的话,再继续进行tryAcquireShared方法的判断,直到该方法的返回值大于等于0,表示此次获取同步状态成功并从自旋退出。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

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

        共享锁的释放是通过调用releaseShared方法实现的。通过自定义实现的tryReleaseShared方法判断,释放成功返回true,否则返回false。因为共享锁支持多个线程同时访问,所以自定义的释放锁的过程以及唤醒后续节点的过程一般是通过CAS和循环来保证同步状态线程安全释放。

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

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

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;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

   独占锁超时获取同步状态:

        通过调用AQS的doAcquireNanos方法,设置线程在指定的时间内获取同步状态,获取成功返回true,否则返回false。该方法可以视作acquire方法的加强版,在acquire方法中,如果线程获取同步状态失败则直接中断;而在doAcquireNanos方法中,在达到设定的超时时间前,都会不停的循环去尝试获取锁。

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

   重入锁:                                                                                                                                                                                                                                                                                                                                                                                                          重入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞。在实现上,只需要解决两个问题,一个是获取锁时,锁需要去识别当前视图获取锁的线程是否为当前占据锁的线程;还一个是释放锁时,线程重复获取了多少次,就要释放多少次。以ReentranLock中的非公平锁为例。

        非公平锁尝试获取锁时,先判断该锁是否被占有,如果是的话,再判断占有锁的线程是否和当前线程是同一个线程,如果是的话,则获取到锁,并将同步状态值增加并返回true。

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

        非公平锁释放锁时,先对同步状态值修改。在ReentranLock中实现的非公平锁中,该方法的传参始终为1。也就是说,每次线程来调用该方法释放锁时,都使同步状态值减1,直到同步状态值减到0才返回true,代表这个锁释放成功,能够被其它线程获取,否则false。

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

   公平锁和非公平锁的区别:

        是否公平是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序应该符合请求的绝对时间顺序,也就是FIFO。

        实现公平锁获取的方法如下。与上面贴的非公平锁的获取方法比较可以发现,二者唯一不同的地方在于,判断条件中公平锁多了一个hasQueuedPredecessors()方法的判断。该方法实际上就是判断同步队列中是否还有其它线程早已在等待,如果有,则返回true,那tryAcquire方法的判断就无法通过。

        由于刚释放锁的线程再次获取同步状态的几率会非常大,所以非公平锁可能使线程“饥饿”,但是因为其相对于公平锁是极少的线程切换,保证了其更大的吞吐量。

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

public final boolean hasQueuedPredecessors() {
    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());
}

   读写锁:

        写锁是一个支持重入的排他锁。在获取写锁的判断中,增加了一个读锁是否存在的判断。如果存在读锁,则写锁不能获取,以保证可见性。写锁的释放与ReentreanLock的释放过程类似。

        读锁是一个支持重入的共享锁。读锁的实现较为复杂,因为它能够被多个线程同时获取,而读状态是所有线程获取读锁次数的综合,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal的子类中,由线程自身维护。但是读锁的复杂部分在于读锁获取次数的计算,在读锁的获取上还是较为简单的,只是判断是否有其它线程获取了写锁。

        读写锁的锁降级指的是写锁降级为读锁。其过程只能为,线程先获取写锁,然后获取读锁,再释放写锁,最后释放读锁。读写锁的所降级主要是为了保证当前线程释放写锁后无法再感知其它线程对数据的更新。

   CONDITION接口:

        Condition接口提供了类似Object类中的监视器方法,与Lock类配合可以实现等待/通知模式,可以看作是Object类监视器方法的加强版。最主要的体现在于,每个Condition维护了一个等待队列,配合Lock使用等同于维护了一个同步队列和多个等待队列,藉此可以实现精确唤醒/等待指定线程。而对线程的操作则是由LockSupport工具类实现的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值