并发编程 -- 同步队列器

昨天整理的不够清晰,今天重新看看了,再次总结了一篇

一,同步队列器

1.核心数据结构

同步器依靠内部的同步队列(fifo的双向队列)来完成同步状态的管理

当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构成一个节点并将该节点加入到同步队列,并且会阻塞当前线程,当同步状态释放时,会把首节点的线程唤醒,使其再次尝试获取同步状态

节点

源码分析:

    static final class Node {

        static final Node SHARED = new Node();

        //标志节点处于独占模式下
        static final Node EXCLUSIVE = null;

        /***********  四种等待状态  **************/
        static final int CANCELLED =  1;

        static final int SIGNAL    = -1;
 
        static final int CONDITION = -2;

        static final int PROPAGATE = -3;
        /***********  四种等待状态  **************/

        //等待状态
        volatile int waitStatus;

        //前驱节点
        volatile Node prev;

        //后继节点
        volatile Node next;
        
        //获得同步状态的线程
        volatile Thread thread;

        //等待队列中的后继节点
        Node nextWaiter;

        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    
        }

        Node(Thread thread, Node mode) {     
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { 
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
基本结构:

在这里插入图片描述

当一个线程成功获取同步状态,则其他线程将无法获取到同步状态,而被构造成节点并将其加入到同步队列中,加入过程保证线程安全

首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时会唤醒后继节点,在后继节点成功获取同步状态后将自己设置成首节点

首节点设置

在这里插入图片描述

只有一个线程能获取到同步状态,不需要CAS保证线程安全

尾节点设置

在这里插入图片描述

二,独占式同步状态的获取和释放

独占式获取同步状态

void acquire(int arg);

独占式获取同步状态(可中断)

void acquireInterruptibly(int arg);

独占式获取同步状态(超时获取)

boolean tryAcquireNanos(int arg,long nanos);

1.独占式获取同步状态(对中断不敏感)

    public final void acquire(int arg) {
        //线程安全的获取同步状态
        //获取失败则构造节点,进入同步队列设置为尾节点
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //对中断不敏感,当对线程进行中断操作时,线程不会从同步队列中移除
            selfInterrupt();
    }

节点构造及加入同步队列

    private Node addWaiter(Node mode) {
        //构造节点
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //尾节点不为空,则尾部快速插入
        if (pred != null) {
            //该节点前驱是原尾节点
            node.prev = pred;
            //CAS设置尾节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //尾节点为空情况下则通过死循环保证节点正确添加
        enq(node);
        return node;
    }

同步器通过死循环来保证节点在高并发下被正确添加,在循环中,只有通过CAS将节点设置成尾节点后,当前线程才从方法中返回,否则,则在循环中不断尝试设置

    private Node enq(final Node node) {
        for (;;) {
            //当前尾节点为空则进行初始化,且当前队列只有一个节点
            Node t = tail;
            if (t == null) { // Must initialize
                //CAS添加
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //尾节点不为空,则尾部快速插入(cas插入)
                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();
                //p是首节点,且尝试获取到同步状态
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //如果p不是首节点,进入等待状态
                //检查和更新无法获取同步状态的节点的状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

检查和更新无法获取同步状态的节点的状态,自旋获取同步状态

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取等待状态
        int ws = pred.waitStatus;
        //(pred)后继节点的线程处于等待状态(node),当前节点(pred)释放了同步状态或者被取消,就会通知后继节点(node)使后继节点得以运行(尝试获取同步状态)
        if (ws == Node.SIGNAL)
            return true;
        //CANCELED状态:进入该状态节点将不会改变    
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

流程图:

在这里插入图片描述

2.独占式响应中断获取

思路差不多,但是,响应线程中断,当线程中断时会抛出异常,线程返回,不会进入同步队列

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

3.释放

线程在获取了同步状态完成相关任务之后,需要释放同步状态给其他线程能够获取同步状态

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //唤醒处于等待状态的线程(头结点后继节点)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

4.总结

独占式在获取同步状态时,维护一个同步队列,获取状态失败的线程会构造成节点并加入到该队列的尾节点处并在该队列中进行自旋,不断尝试获取同步状态,移出队列或者停止自旋的条件就是前驱节点称为头结点并且获得同步状态,在头结点释放同步状态时,同步器会调用方法释放同步状态,并且唤醒后继节点

三,共享式同步状态的获取和释放

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

同步器调用tryAcquireShared方法尝试获取同步状态,tryAcquireShared返回值是一个int类型,当返回值大于0时,表示能够获取到同步状态。

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

1.共享式获取同步状态

    private void doAcquireShared(int arg) {
        //构造一个标志位共享节点的节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            //结束自旋状态的条件就是tryAcquireShared返回值大于0。此时获取到同步状态
            for (;;) {
                //获取前驱节点
                final Node p = node.predecessor();
                //如果前驱是头结点,尝试获取同步状态
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    //如果同步状态返回>=0,则成功获取到同步状态,并返回
                    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);
        }
    }

2.释放

释放同步状态后,将唤醒处于后续等待状态中的节点

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

doReleaseShared()通过循环和CAS保证释放同步状态的线程安全性

    private void doReleaseShared() {
    /*
     * 下面的循环在 head 节点存在后继节点的情况下,做了两件事情:
     * 1. 如果 head 节点等待状态为 SIGNAL,则将 head 节点状态设为 0,并唤醒后继节点
     * 2. 如果 head 节点等待状态为 0,则将 head 节点状态设为 PROPAGATE,保证唤醒能够正
     */
        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;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值