AbstractQueuedSynchronizer相关总结

介绍

提供了一个基于FIFO队列,可以用于构建锁或其他相关同步装置的基础框架。

核心成员变量如下:

  1. 该同步器使用了一个state(int类型)来表示状态,期望它能成为大部分同步需求的基础。
  2. 提供了一个FIFO队列(双向链表),Node元素保存着线程引用和线程状态的容器,每个线程对同步器的访问,都可以看做是队列中的一个节点。
//等待队列的头,延迟初始化。除了初始化,只能通过setHead方法进行修改。
//如果head存在,则保证其waitStatus不会取消
Node head;
//等待队列的尾,延迟初始化。只能通过enq添加新的等待节点
Node tail;
//同步状态
int state;

Node解析

节点成为sync队列和condition队列构建的基础,在同步器中就包含了sync队列。同步器拥有三个成员变量:sync队列的头结点head、sync队列的尾节点tail和状态state。对于锁的获取,请求形成节点,将其挂载在尾部,而锁资源的转移(释放再获取)是从头部开始向后进行。对于同步器维护的状态state,多个线程对其的获取将会产生一个链式的结构。

class final Node{
       //指示节点正在共享模式下等待的标记
       Node SHARED = new Node();
       //指示节点正在以独占模式等待的标记
       Node EXCLUSIVE = null;
     /**
        当前节点状态,包括如下状态:
        SIGNAL:值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
        CANCELLED:值为1,表示当前的线程被取消
        CONDITION:值为-2,表示当前节点在等待condition,也就是在condition队列中
        PROPAGATE:值为-3,表示当前场景下后续的acquireShared能够得以执行
        值为0,表示当前节点在sync队列中,等待着获取锁。  
     **/   
     int waitStatus;
     //前一个节点
     Node prev;
     //后一节点
     Node next;
     //当前阶段绑定的线程
     Thread thread;
     // 存储condition队列中的后序节点 
     Node nextWaiter;
}

 

使用点

  1. ReentrantLock、ReentrantReadWriteLock中的Sync
  2. Semaphore中的Sync
  3. CountDownLatch中的Sync,释放时,有序释放所有正在等待的线程
  4. ThreadPoolExecutor中的Worker,Worker是工作线程,为了避免可重入,单独实现锁控制

核心

AbstractQueuedSynchronizer只维护队列,具体的锁定逻辑有各自的子类实现来定,.

比如下面的tryAcquire等方法,注意其中的参数arg,这个参数一般用来指定一定申请几个,一般是1,这个变量对应于AQS中state变量的变化,一般是state=state+arg,

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

ReentrantLock传递arg=1,那么state也会大于等于1,这样也就保证state==0时,肯定可以获得锁,大于0时不一定,重入时state会继续加一,也就会存在state>1的情况。所以ReentrantLock多次使用时,需要多次释放

Semaphore中的public boolean tryAcquire(int permits) {},此时方法参数就变成了需要申请的许可量,那么底层的state就对应于总的许可量,每次申请时,state会减去相应的许可量

ReentrantReadWriteLock读写锁,state中,高16位为读锁,低16位为写锁,高低位记录各自的锁个数,比如当前高位不等于0时,就说明当前有多少个数的读锁正在进行。

通过上面的分析,底层的state变量很关键,基本上锁个数的控制都是由它来控制的

总结

  1. AQS的共享模式和排他模式,共享锁就是允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有。排它锁,也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。参考:https://www.cnblogs.com/panxuejun/p/8874321.html
  2. CountDownLatch是通过默认count设置给state,这样state>1时代表被占有了锁,等调用countDown(),state==0时,会唤醒第一个Node等待节点,发现是共享模式,会继续传播唤醒后续共享模式的Node节点,直到没有共享模式节点或遇到排他模式节点时停止唤醒
  3. Lock的lockInterruptibly支持锁中断lock不支持锁线程终端,lockInterruptibly是发现线程中断的标识,直接当前线程抛出异常,lock是发现线程中断标识后,忽略继续执行https://blog.csdn.net/fangzuo/article/details/71080469

其他总结

ReentrantReadWriteLock

可重复读写锁,

关键点:

1、state中,高16位为读锁,低16位为写锁,高低位记录各自的锁个数,比如当前高位不等于0时,就说明当前有多少个数的读锁正在进行。

2、底层也是采用AQS使用,读和写都维护在一个链表中,抢占锁时,公平锁会依次塞入链表,非公平会先判断是否可获得锁

3、当一个读锁或写锁释放时,会拿到链表中一条要求读或者写的数据,继续抢占读锁或写锁

4、读锁的获取和释放:除了state记录总重入锁个数,在每个获取到读锁的线程中,还分别通过threadLocal绑定了各自线程读锁重入的次数(计数器),记录目的:这是为了实现jdk1.6中加入的getReadHoldCount()方法的,这个方法能获取当前线程重入共享锁的次数(state中记录的是多个线程的总重入次数)

线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。

   在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。

   仔细想想,这个设计是合理的:因为当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

综上:

一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。

Condition

怎么实现等待?

    白话:如同下面的awaitNanos方法,基本上是通过while循环不断的计算剩余时间,来实现指定时间的等待。其中里面,如果时间剩余大于1秒则会通过LockSupport先暂停线程执行,当小于1秒时才用while来精确计算等待。

 // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
 //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;
 //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。    
 public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            long savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return deadline - System.nanoTime();
        }
 //使当前线程加入 await() 等待队列中,并释放当锁,当其他线程调用signal()会重新请求锁。与Object.wait()类似。
    void await() throws InterruptedException;

    //调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
    //调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。 
    void awaitUninterruptibly();

    // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
    //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;
    //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。 
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    //与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。
    boolean await(long time, TimeUnit unit) throws InterruptedException;

   //适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。
    boolean awaitUntil(Date deadline) throws InterruptedException;
    
    //唤醒一个在 await()等待队列中的线程。与Object.notify()相似
    void signal();

   //唤醒 await()等待队列中所有的线程。与object.notifyAll()相似
    void signalAll();
}

ConditionObject

通过ConditionObject我们可以实现和synchronized下的wait同样的效果

package com.quzf.thread.orderlyThread;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description 多线程有序依赖执行
 * @Author quzf
 * @Create 2018-12-20 11:07 AM
 */
@Slf4j
public class OrderlyThreadWithLockTest {

    public static ReentrantLock lock = new ReentrantLock();
    //通过condition,我们可以实现和synchronized下的wait同样的效果
    public static Condition condition = lock.newCondition();


    @Data
    @NoArgsConstructor
    static class ThreadTest extends Thread {
        private ThreadTest dependThread;

        private String out;
        private volatile boolean isComplete = false;

        public ThreadTest(String out) {
            this.out = out;
        }

        @Override
        public void run() {
            lock.lock();
            while (dependThread != null && !dependThread.isComplete()) {
                //                    lock.unlock();
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info(out);
            this.isComplete = true;
            condition.signalAll();
            lock.unlock();

        }

    }

    public static void main(String[] args) throws InterruptedException {
        ThreadTest thread1 = new ThreadTest("Thread1 run");
        ThreadTest thread2 = new ThreadTest("Thread2 run");
        ThreadTest thread3 = new ThreadTest("Thread3 run");
        thread3.setDependThread(thread2);
        thread2.setDependThread(thread1);
        //        thread1.setDependThread(thread2);

        thread3.start();
        Thread.sleep(1000);
        thread2.start();
        Thread.sleep(1000);
        thread1.start();
        //        System.out.println(thread3.dependThread);
    }
}

总结:

其实看到这么多,也算基本了解底层锁的实现原理,所以此种锁的方式和java内置的syncized、wait是不一样的

上面的锁都是基于CAS和

LockSupport来保障原子性、实现线程等待的

 

FutureTask

白话:可等待式的线程,默认的runable是没有返回值的,如果想要阻塞式的获取异步线程的返回值,就要用到FutureTask类

CyclicBarrier

白话:它允许一组线程互相等待,直到到达某个公共屏障点,然后释放这些线程,重置屏障点继续等待,直到所有要执行的线程都执行完毕

CyclicBarrier是CountDownLatch的升级版,功能更复杂强大 

Cyclic意为循环,就是说可以反复使用。比如指定计数器为10,当凑齐第一批10个线程后,计数器自动归0;接着下一批… 

应用场景:10个人一组一组的完成某个任务。

Exchanger

白话:Exchanger类源于java.util.concurrent包,它可以在两个线程之间传输数据,Exchanger中的public V exchange(V x)方法被调用后等待另一个线程到达交换点(如果当前线程没有被中断),然后将已知的对象传给它,返回接收的对象。

如果另外一个线程已经在交换点等待,那么恢复线程计划并接收通过当前线程传给的对象

ArrayBlockingQueue

白话:阻塞队列,底层主要使用ReentrantLock、Condition notEmpty、Condition notFull来实现阻塞队列,基本上所有的方法先来者占用了锁,后来的基本上都需要等待。

CountDownLatch

countDownLatch.await()方法执行时,执行acquireSharedInterruptibly方法,先tryAcquireShared,此时state!=0,所以返回-1(获取共享锁失败),然后执行doAcquireSharedInterruptibly

 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

 

 protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

doAcquireSharedInterruptibly方法,所有线程都在此处等待

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //新建一个共享模式的节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //获取上一个节点
                final Node p = node.predecessor();
                //判断上节点是否是head节点
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    //第一次进入时,此时state!=0,r<0继续后面执行
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //第一次进入时,获取共享锁失败,把线程进行park锁定,当前线程在for循环处停止
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 countDownLatch.countDown()方法执行时,执行releaseShared,先tryReleaseShared释放共享锁,当state=0时,tryReleaseShared获取共享锁成功,返回true,才执行doReleaseShared方法

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
 protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

doReleaseShared方法执行

 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
                    //开始释放head下一个等待节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

setHeadAndPropagate方法

 private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
       
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
}

关键点:当前doReleaseShared执行后,unpark释放了head下一个等待节点。会触发上方doAcquireSharedInterruptibly方法中的for循环其中一个线程的执行,获取到共享锁时,触发setHeadAndPropagate方法执行,setHeadAndPropagate方法会先修改head后续节点,然后继续调用doReleaseShared方法。就这样通过不断的doReleaseShared方法,通过转播机制把所有共享模式的节点线程都进行释放,直到没有共享模式的节点为止。

doReleaseShared释放线程-》触发doAcquireSharedInterruptibly线程执行-》触发doReleaseShared-》触发doAcquireSharedInterruptibly线程执行-》直到没有共享模式的节点停止

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值