Java源码解读系列10—SynchronousQueue(非公平模式,JDK1.8 )

1 概述

  • SynchronousQueue(简称SQ)是BlockingQueue接口的实现类,是一种阻塞、线程安全的、不存储任何元素的同步队列。SQ的底层实现分为两种,一种是非公平模式(默认),采用栈的数据结构,先入队的数据,后出队,即可LIFO;另外一种是公平模式,采用队列的数据结构,先入队的数据,先出队,即FIFO。

  • SQ跟LinkedBlockingQueue(LBQ)和ArrayBlockingQueue(ABQ)等阻塞队列,最大一个不同点是一个线程t1执行put操作后,必须有另外线程t2执行take操作,t1才能继续添加,否则t1会被阻塞。从这个角度去理解,才认为SQ本身不存储数据。在性能方面,由于SQ不存储数据,吞吐量高于LBQ和ABQ

  • 本文主要讲解SQ非公平模式实现方法

2 用例

使用SynchronousQueue队列可以快速构建一个生产者消费者的模型,因为生产者线程执行添加操作后会发生阻塞,除非有消费者线程取出才能会被唤醒。反正,如果消费者消费不到数据也会发生阻塞,直至有生产者执行添加操作。

public class SynchronousQueueApp {
    //同步队列容器
    //默认非公平锁
    private static SynchronousQueue queue = new SynchronousQueue();

    /**
     * 生产方法
     */
    private static void product(){
        try {
            String content = System.currentTimeMillis() + "";
            queue.put(content);
            String tName = Thread.currentThread().getName();
            System.out.println(tName + "生产了" + content);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    /**
     * 消费方法
     */
    private static void consumer(){
        try {
            Object content = queue.take();
            String tName = Thread.currentThread().getName();
            System.out.println(tName + "消费了" + content);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception{
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,
                10,
                0,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());


        Thread productThread = new Thread(new Runnable() {
            @Override
            public void run() {
                 product();

            }
        });


        Thread consumerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                    consumer();
            }
        });

        executor.execute(productThread);
        Thread.sleep(3000);
        executor.execute(consumerThread);
        executor.shutdown();

    }
}

控制台打印信息:

pool-1-thread-2消费了1617525421976
pool-1-thread-1生产了1617525421976

3 构造函数

SynchronousQueue默认采用非公平模式

public SynchronousQueue() {
      //默认是非公平模式
        this(false);
    }

 public SynchronousQueue(boolean fair) {
       //true:公平模式,使用FIFO队列进行存储数据
       //false:f非公平模式,使用LIFO栈进行存储数据
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
 }

4 存储数据节点SNode

static final class SNode {
   //指向下一个节点
    volatile SNode next;        // next node in stack
    //存储当前节点的匹配节点
    //match为当前节点本身,说明当前节点被取消了
    volatile SNode match;       // the node matched to this
    //存储当前线程,用于阻塞/被唤醒
    volatile Thread waiter;     // to control park/unpark

    //数据项,若item存储的数据为节点本身,说明该节点被取消了
    Object item;                // data; or null for REQUESTs
    //节点的模式:
    //1)mode = 0, 属于REQUEST模式,即取出模式 
    //2)mode = 1,属于DATA模式 ,插入模式 
    //3)mode = 2,属于FULFILLING模式,说明此节点找到与之匹配的节点。若当前节点是取出,则找到添加节点,反之亦然。
    int mode;

    //有参构造函数  
    SNode(Object item) {
        this.item = item;
    }

    //通过CAS更新下一个节点
    boolean casNext(SNode cmp, SNode val) {
        return cmp == next &&
            UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }


     //当前节点尝试与节点s进行匹配
    boolean tryMatch(SNode s) {
         //match等于null,说明当前节点还没有被匹配到
        if (match == null &&
             //通过CAS操作将当前节点的match指向s
            UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
            //匹配成功
            //w为当前节点存储的的阻塞线程
            Thread w = waiter;
            //w非空
            if (w != null) {    // waiters need at most one unpark
               //将waiter上的引用清空
                waiter = null;
                //唤醒等待线程w
                LockSupport.unpark(w);
            }
            //返回匹配成功
            return true;
        }
        
        //匹配失败
        //判断当前节点的match引用是否为s
        return match == s;
    }

  
     //尝试取消当前节点
     //取消方式是使当前节点保存
    void tryCancel() {
        UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
    }

    //判断当前节点是否被取消
    boolean isCancelled() {
        return match == this;
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long matchOffset;
    private static final long nextOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = SNode.class;
            matchOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("match"));
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

5 添加操作(put方法)

public void put(E e) throws InterruptedException {
       //非空检查
        if (e == null) throw new NullPointerException();
        //e:待插入元素   false:表示没有设置超时时间,超时时间不起作用  0:超时时间
        if (transferer.transfer(e, false, 0) == null) {
            //中断标记
            Thread.interrupted();
            //抛出异常
            throw new InterruptedException();
        }
  }
    

6 取出操作(take方法)

跟put方法类似,都是调用transfer方法,唯一不同的是入参的元素为null

 public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
         //返回的数据不为mull
        if (e != null)
            //返回获取的数据
            return e;
         //返回的数据mull
         //标记中断
        Thread.interrupted();
        //抛出异常
        throw new InterruptedException();
    }

7 图解节点的入队和出队

SQ的代码相对LBQ和ABQ复杂多了,在深入源码之前,建议先看下添加和取出的过程图。

executor.execute(productThread);
Thread.sleep(3000);
executor.execute(productThread);
Thread.sleep(3000);
executor.execute(consumerThread);
Thread.sleep(3000);
executor.execute(consumerThread);

1)t1执行put操作,new一个SNode1节点,并且将SNode1入栈,head指向SNode1,t1线程阻塞
在这里插入图片描述
2)t2执行put操作,发现头节点不为空,且头节点的模式跟自己一样。t2 new一个SNode2节点,并且将SNode2入栈,head指向SNode2,t2线程阻塞
在这里插入图片描述
3) t3执行take操作,不直接将SNode2取出,而是new一个SNode3,并且将SNode3压入栈
在这里插入图片描述
SNode3去找匹配节点,发现SNode2是put操作,跟自己匹配,两个节点一起出队,唤醒t2
在这里插入图片描述
4)t4执行take操作,new一个SNode4,并且将SNode4压入栈

在这里插入图片描述
SNode4去找匹配节点,发现SNode1是put操作,跟自己匹配,两个节点一起出队,唤醒t1
在这里插入图片描述

8 取出和添加的公共操作

8.1 transfer方法

E transfer(E e, boolean timed, long nanos) {
    //待插入节点
    SNode s = null; // constructed/reused as needed
    //通过put方法进来,e不为null,因此待插入节点模式为DATA
    int mode = (e == null) ? REQUEST : DATA;
   //无限制循环
    for (;;) {
        //将头节点赋予h
        SNode h = head;
        //头节点为空或者头节点的模式等于跟待插入节点的模式
        if (h == null || h.mode == mode) {  // empty or same-mode
            //超时处理
            if (timed && nanos <= 0) {      // can't wait
                if (h != null && h.isCancelled())
                    casHead(h, h.next);     // pop cancelled node
                else
                    return null;
              //s为空则创建新的节点 
              //修改s节点的模式,并将s下一个节点的引用指向h
              //通过CAS操作将头节点从h更新为s
            } else if (casHead(h, s = snode(s, e, h, mode))) {
               //等待直到有另外一个线程执行相反的操作,put对应take,反之亦然
                SNode m = awaitFulfill(s, timed, nanos);
                //m为s的match值,m等于s说明s被取消了
                if (m == s) {               // wait was cancelled
                    //将s从栈中移除
                    clean(s);
                    //返回空
                    return null;
                }
                //头节点不为空且头节点的下一个节点指向s
                if ((h = head) != null && h.next == s)
                    //s节点匹配成功,通过CAS将s的下一个节点更新为头节点
                    casHead(h, s.next);     // help s's fulfiller
                //如果当前线程的调用的是取出方法进来,即REQUEST模式,返回m.item;否则返回s.item
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
        
        //判断h是否为FULFILLING模式
        } else if (!isFulfilling(h.mode)) { // try to fulfill
           //h被取消
            if (h.isCancelled())            // already cancelled
               //将h弹出栈
                casHead(h, h.next);         // pop and retry
             //s为空则创建新的节点 
            //修改s节点的模式为FULFILLING|mode,并将s下一个节点的引用指向h
            //通过CAS操作将头节点从h更新为s   
            e     
                //无限循环
                for (;;) { // loop until matched or waiters disappear
                   //s的下一个节点
                    SNode m = s.next;       // m is s's match
                    //m为空
                    if (m == null) {        // all waiters are gone
                       //将头节点从s更新为null
                        casHead(s, null);   // pop fulfill node
                        //释放s节点
                        s = null;           // use new node next time
                        //返回最外层的for循环
                        break;              // restart main loop
                    }
                    //mn为s的下下个节点
                    SNode mn = m.next;
                    //m尝试与节点s进行匹配
                    if (m.tryMatch(s)) {
                       //匹配成功,将头节点从s更新为mn,让s和m出栈
                        casHead(s, mn);     // pop both s and m
                         //如果当前线程的调用的是取出方法进来,即REQUEST模式,返回m.item;否则返回s.item
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                  // lost match
                       //m节点与其他节点已经匹配,将s的下一个节点指向mn
                       s.casNext(m, mn);   // help unlink
                }
            }
            
      //走到这一步,说明栈顶的模式为FULFILLING,而且没有出队, 此时需要帮助栈顶找到匹配节点
        } else {                            // help a fulfiller
            SNode m = h.next;               // m is h's match
            //h的下一个节点为空
            if (m == null)                  // waiter is gone
               //将头节点更新为空
                casHead(h, null);           // pop fulfilling node
            else {
                SNode mn = m.next;
                //m与h进行匹配
                if (m.tryMatch(h))          // help match
                   //匹配成功,将h和m出栈
                    casHead(h, mn);         // pop both h and m
                else                        // lost match
                    //匹配不成功,将h的下一个节点指向mn
                    h.casNext(m, mn);       // help unlink
            }
        }
    }
} 

8.2 snode方法

static SNode snode(SNode s, Object e, SNode next, int mode) {
            //s为null则创建节点
            if (s == null) s = new SNode(e);
             //将s节点的模式修改为mode
            s.mode = mode;
             //将s节点下一个节点的引用指向next节点
            s.next = next;
            return s;
        }

8.3 casHead

更新栈的头节点

 boolean casHead(SNode h, SNode nh) {
    //判断h是否等于头节点,若不是说明被其他线程修改过
    return h == head &&
        //使用CAS将nh替换h
        UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
}

8.4 awaitFulfill方法

//获取服务器的核心数
static final int NCPUS = Runtime.getRuntime().availableProcessors();

//当前线程阻塞前的自旋次数,现在的服务器基本属于多核服务器,因此maxTimedSpins一般为32
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

//当前线程阻塞前的自旋次数,多核服务器下,maxUntimedSpins一般为32 * 16 = 512
 static final int maxUntimedSpins = maxTimedSpins * 16;

//自旋超时阀值,超过这个数值的才会发生阻塞
static final long spinForTimeoutThreshold = 1000L;

SNode awaitFulfill(SNode s, boolean timed, long nanos) {
            //超时时间,time为false时,deadline为0 
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            //获取当前线程
            Thread w = Thread.currentThread();
            //通过shouldSpin判断是否需要自旋,ture为需要
            //spins表示自旋次数
            int spins = (shouldSpin(s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
             //无限循环
             //自旋
            for (;;) {
               //判断当前线程是否被中断
                if (w.isInterrupted())
                     //将当前节点的match指向当前节点
                    s.tryCancel();
                 //将s的匹配节点赋予m
                SNode m = s.match;
                //匹配节点不为空
                if (m != null)
                    //直接返回匹配的节点
                    return m;
                 //超时处理
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel();
                        continue;
                    }
                }
                //判断自旋次数是否大于0
                if (spins > 0)
                      //shouldSpin为false,表示其他线程更改过头节点                  
                     //shouldSpin为true,自旋次数减去1
                    spins = shouldSpin(s) ? (spins-1) : 0;
                //自旋次数为0且当前节点的waiter为空
                else if (s.waiter == null)
                   //将当前线程添加到s的waiter中           
                    s.waiter = w; 
                 //自旋次数为0且没有设置超时时间
                else if (!timed)
                    //阻塞当前线程
                    //通过s节点的waiter可以找到阻塞的线程并将其唤醒
                    LockSupport.park(this);
                    //如果nanos小于spinForTimeoutThreshold,因为1000纳秒的超时等待无法做到十分的精确,而且非常断,干脆不发生阻塞
                else if (nanos > spinForTimeoutThreshold)                
                     //阻塞指定超时时间
                    LockSupport.parkNanos(this, nanos);
            }
        }

8.5 shouldSpin方法

判断是否自旋

boolean shouldSpin(SNode s) {
    SNode h = head;
    //满足三者之一:1)s节点等于头节点 2)头节点为空  3)头节点的模式为FULFILLING
    //这样设计有个好处,因为非公平模式采用栈的结构,当节点s已经非头节点,说明它不是马上出队的节点,因此不需要再浪费CPU了
    return (h == s || h == null || isFulfilling(h.mode));
}

8.6 isFulfilling方法

判断节点模式是否为FULFILLING模式

 static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }

8.7 clean方法

删除节点s,最差条件下,可能需要遍历整个栈才能删除s

       //将s从栈中移除
        void clean(SNode s) {
            //清空i数据项
            s.item = null;   // forget item
            //清空waiter中存储的线程
            s.waiter = null; // forget thread     
          
            //s的下一个节点
            SNode past = s.next;
            //past不为空且past被取消
            if (past != null && past.isCancelled())
                //past为s的下下个节点
                past = past.next;

            // Absorb cancelled nodes at head
            SNode p;
            
            //从栈顶开始移除被取消的节点
            while ((p = head) != null && p != past && p.isCancelled())
               //(p为被取消节点,将p出队
                casHead(p, p.next);

            //将p节点之后,past节点之前的被取消节点移出栈
            while (p != null && p != past) {    // Unsplice embedded nodes
                SNode n = p.next;
                //节点n不为空且n被取消
                if (n != null && n.isCancelled())
                   //将p的下一个节点指向n的下一个节点
                    p.casNext(n, n.next);
                else
                   //若n为空,终止while循环
                    p = n;
            }
        }

9 参考文献

1)JDK7在线文档
https://tool.oschina.net/apidocs/apidoc?api=jdk_7u4
2) JDK8在线文档
https://docs.oracle.com/javase/8/docs/api/
3) Bruce Eckel. Java编程思想,第4版,2007,机械工业出版社
4)方腾飞,魏鹏,程晓明. Java并发编程的艺术,第1版,2015年,机械工业出版社

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值