java-01-源码篇-04 Java集合-03-BlockingQueue(三)

       

目录

一,Leader-Follower模式

1.1 leader-follower 测试案例

二,DelayQueue

2.1 Delayed

2.2 Delayed案例

 2.3 DelayQueue 属性分析

2.4 DelayQueue 构造器

2.5 DelayQueue 新增业务

2.5.1 第一种新增:操作失败抛异常

2.5.2 第二种新增:操作失败返回特殊值 

2.5.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止

2.5.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理或者操作成功也会释放锁

2.6 DelayQueue 删除业务

2.6.1 第一种删除:操作失败抛异常

2.6.2 第二种删除:操作失败返回特殊值

2.6.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止

2.6.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁

2.7 DelayQueue 检索业务

2.8 DelayQueue 扩容机制

三,TransferQueue 传输队列

3.1 LinkedTransferQueue 子类

3.2 LinkedTransferQueue 内部类

3.2.1 Node 类

3.2.1.1 Node 构造器

3.2.1.2 Node的属性

3.2.1.3 Node的业务

3.3 LinkedTransferQueue 属性

3.4 LinkedTransferQueue 新增业务

3.4.1 第一种新增:操作失败抛异常

3.4.2 第二种新增:操作失败返回特殊值

3.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止

3.4.4  第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理或者操作成功也会释放锁

3.5 LinkedTransferQueue 删除业务

3.5.1 第一种删除:操作失败抛异常

3.5.2 第二种删除:操作失败返回特殊值

3.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止

3.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁

3.7 LinkedTransferQueue 传输业务

3.8 LinkedTransferQueue 其他业务

3.8.1 获取第一个节点

3.8.2 统计模型中不匹配的节点

 3.8.3 CAS更新业务       

  3.8.4 跳过已匹配的节点(即“死节点”)来保持队列的连贯性和高效性


        上一章:java-01-源码篇-04 Java集合-03-BlockingQueue(二)-CSDN博客

        这张沿着上一章继续讲解剩余几个没有讲解完的BlockingQueue 子类阻塞队列。还剩余图中四个红圈圈的实例没有讲解。继续进行讲解

        在讲解DelayQueue之前先讲解一下leader-follower模式。

一,Leader-Follower模式

        Leader-Follower 模式是一种常用的并发设计模式,用于高效地管理多个工作线程处理事件驱动任务。该模式的核心思想是通过一组工作线程来轮流充当Leader,负责处理新的时间并将其分发给其他线程(Followers)。这有助于减少上下文切换和竞争,提高系统的并发性能和吞吐量。

        Leader-Follower 模式中一个重要的概念:

        Leader: 当前负责处理新事件的线程

        Follower: 等待被唤醒以处理任务的线程

        Event Queue: 存储待处理事件的队列

        Event Handler: 处理事件的逻辑

1.1 leader-follower 测试案例

/**
 * 事件
 */
class ToastEvent {
    private final String message;

    public ToastEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
/**
 * 事件处理器
 */
class ToastEventHandler {
    /**
     * 处理event事件业务
     * @param event
     */
    public void handle(ToastEvent event) {
        System.out.println(Thread.currentThread().getName() + " handling event: " + event.getMessage());
    }
}
/**
 * 工作线程,轮流充当Leader处理事件
 */
class Worker implements Runnable {

    /** event queue 事件队列 */
    private static final BlockingQueue<ToastEvent> eventQueue = new LinkedBlockingQueue<>();

    /** 事件处理器 */
    private static final ToastEventHandler eventHandler = new ToastEventHandler();

    /** 领导者:true - 已选举出来 | false - 未选择出来 */
    private static volatile boolean leader = false;

    @Override
    public void run() {
        while (true) {
            if (becomeLeader()) { // 选举出成为Leader
                try {
                    System.out.println(Thread.currentThread().getName() + " became Leader");
                    ToastEvent event = eventQueue.take();
                    eventHandler.handle(event);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } finally {
                    leader = false;
                }
            } else { // 否则成为 Follower进行等待
                System.out.println(Thread.currentThread().getName() + " is Follower");
                Thread.yield(); // 优雅的让出CPU
            }
        }
    }

    /**
     * 选择出leader
     * @return
     */
    private boolean becomeLeader() {
        synchronized (Worker.class) {
            if (!leader) {
                leader = true;
                return true;
            }
            return false;
        }
    }

    /**
     * 添加事件
     * @param event
     */
    public static void addEvent(ToastEvent event) {
        eventQueue.add(event);
    }
}
public class TestLeaderFollower {
    public static void main(String[] args) {
        int numberOfWorkers = 3;
        // 创建3个工作线程
        for (int i = 0; i < numberOfWorkers; i++) {
            new Thread(new Worker(), "工作线程-" + i).start();
        }

        // 第一次添加10个event事件,让3个工作线程进行处理
        for (int i = 0; i < 10; i++) {
            Worker.addEvent(new ToastEvent("处理事件-" + i));
        }
    }
}

        本次案例中ToastEvent 表示需要处理的事件,ToastEventHandler 表示事件处理器,里面编写着处理事件的业务逻辑; 也就是handler()方法

    public void handle(ToastEvent event) {
        System.out.println(Thread.currentThread().getName() + " handling event: " + event.getMessage());
    }

        最后定义Worker 工作线程类,该类的作用是用来定义工作流程,也就是说用来实现Leader-Follower模式的轮流充当Leader处理事件。通过becomeLeader()来选举出leader来。

    private boolean becomeLeader() {
        synchronized (Worker.class) {
            if (!leader) {
                leader = true;
                return true;
            }
            return false;
        }
    }

        选择出来的成为 Leader ;没有的则成为 Follower;代码如下

    @Override
    public void run() {
        while (true) {
            if (becomeLeader()) { // 选举出成为Leader
                try {
                    System.out.println(Thread.currentThread().getName() + " became Leader");
                    ToastEvent event = eventQueue.take();
                    eventHandler.handle(event);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } finally {
                    leader = false;
                }
            } else { // 否则成为 Follower进行等待
                System.out.println(Thread.currentThread().getName() + " is Follower");
                Thread.yield(); // 优雅的让出CPU
            }
        }
    }

        这里的案例之所以使用 while(true) 一直循环等待,表示的是本次追加的3个事件;进行处理,处理完之后,三个Worker工作线程都进入Follower等在状态;等待下一次事件添加将继续进行处理事件;这样就达到了循环使用线程;从而减少了上下文的切换;

public class TestLeaderFollower {
    public static void main(String[] args) throws InterruptedException {
        int numberOfWorkers = 3;
        // 创建3个工作线程
        for (int i = 0; i < numberOfWorkers; i++) {
            new Thread(new Worker(), "工作线程-" + i).start();
        }

        // 第一次添加10个event事件,让3个工作线程进行处理
        System.out.println("【第一次】添加10个事件");
        for (int i = 0; i < 10; i++) {
            Worker.addEvent(new ToastEvent("one-batch-event-" + i));
        }

        System.out.println("【第一次处理完】睡眠三秒");
        TimeUnit.SECONDS.sleep(3);

        System.out.println("【第二次】再添加10个事件");
        for (int i = 0; i < 10; i++) {
            Worker.addEvent(new ToastEvent("two-batch-event-" + i));
        }
    }
}

        本次输出,为了方便观察把 Follower 状态的输出语句给注释了。从上面可以得知把第一次事件处理完之后,都将进行Follower状态,等待新事件加入之后,再次通过轮流leader进行处理事件!

         本次案例主要是为了更好理解什么是Leader-Follower模式!因为在DelayQueue 延迟队列里面运用到了Leader-Follower 设计模式

二,DelayQueue

        DelayQueue(延迟队列),该队列是没有界限的,也就意味着有扩容机制。如果是固定大小集合则没有扩容机制;该队列中新增的元素的只有其指定的时间已延期过时才能够被拿到;没有还没有延迟过期则拿到的内容为null;继承结构如下:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {}

        从中可以看出DelayQueue 的元素只能是Delayed 这个类的子类; 

2.1 Delayed

        刚才我们讲了DelayQueue 延迟队列只返回元素已延迟过时的对象,那这个判断该怎么实现?在JDK1.5提供一个 java.util.concurrent.Delayed 接口;该接口为已延迟对象提供一个标准处理方法getDelay;getDelay() 获取对象的剩余时间,如果剩余时间是0或者是负数表示已过时;代码如下:

package java.util.concurrent;

/**
 * @since 1.5
 * @author Doug Lea
 */
public interface Delayed extends Comparable<Delayed> {

    /**
     * 在给定的时间单位内返回与此对象关联的剩余延迟
     * @param unit 时间单位
     * @return 返回剩余延迟时间;0或者负数表示延迟时间已过期
     */
    long getDelay(TimeUnit unit);
}

        从源码中可以得知Delayed接口还是Comparable 接口的子类;意味着该Delayed接口还具有可比较的特性;通过 Comparable的compareTo来实现。

2.2 Delayed案例

/**
 * @author toast
 * @time 2024/5/15
 * @remark 自定义一个延迟实体
 */
class ToastDelayTask implements Delayed {

    /**
     * 延迟时间
     */
    private final long delayTime;

    /**
     * 过期时间
     */
    private final long expireTime;

    /**
     * 消息内容
     */
    private final String message;

    public ToastDelayTask(long delay, String message) {
        this.delayTime = delay;
        this.expireTime = System.currentTimeMillis() + delay;
        this.message = message;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        long diff = expireTime - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        if (this.expireTime < ((ToastDelayTask) o).expireTime) {
            return -1;
        }
        if (this.expireTime > ((ToastDelayTask) o).expireTime) {
            return 1;
        }
        return 0;
    }

    @Override
    public String toString() {
        return message;
    }
}
/**
 * @author toast
 * @time 2024/5/15
 * @remark
 */
public class TestDelayedQueue {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Delayed> queue = new DelayQueue<>();

        queue.put(new ToastDelayTask(2000, "【任务一】2秒延迟时间"));
        queue.put(new ToastDelayTask(4000, "【任务二】4秒延迟时间"));
        queue.put(new ToastDelayTask(1000, "【任务三】1秒延迟时间"));

        while (!queue.isEmpty()) {
            long startTime = System.currentTimeMillis();
            Delayed task = queue.take();
            long endTime = System.currentTimeMillis();
            System.out.println("开始时间:" + startTime);
            System.out.println("结束时间:" + endTime);
            System.out.println("时间间隔:" + (endTime - startTime));
            System.out.println("Executed: " + task);
        }
    }
}

 2.3 DelayQueue 属性分析

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {
    
    /** ReentrantLock 可重入锁 被transient锁修饰意味着不可被序列化 */
    private final transient ReentrantLock lock = new ReentrantLock();
    /** 
     * q-延迟队列,从这里大概可以得知DelayQueue 延迟队列是借助PriorityQueue优先级队列实现的
     * 也就是意味着延迟队列的新增,删除,检索都是调用PriorityQueue优先级队列的方法;
     * 只不过是在为延迟业务在包一层,对延迟时间做一层处理;这样就达到了延迟队列的需求;
     */
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /**
     * Leader-Follower 模式中的Leader;可以看出该leader直接是一条线程;
     */
    private Thread leader;

    /**
     * 该Condition 表示可用的Leader;当需要进行leader与Follower轮流时通过这个来做判断;
     * 所以属性名称叫做available  表示可用的 (leader)
     */
    private final Condition available = lock.newCondition();
    // ......
}

2.4 DelayQueue 构造器

    public DelayQueue() {}
    /**
     * 创建一个延迟队列,并将指定的元素集添加到延迟队列里面
     * @param c 元素集
     */
    public DelayQueue(Collection<? extends E> c) {
        this.addAll(c);
    }

    /** 调用的是AbstractQueue的addAll 方法 */
    public boolean addAll(Collection<? extends E> c) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

2.5 DelayQueue 新增业务

2.5.1 第一种新增:操作失败抛异常

    /**
     * 新增元素
     * @param e 要新增的元素
     */
    public boolean add(E e) {
        return offer(e);
    }

2.5.2 第二种新增:操作失败返回特殊值

    /**
     * 新增元素
     * @param e 新增元素
     */
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e); // 委托调用优先级队列的新增
            /*
             * 进行队首进行检索,如果刚刚新增的元素是队首的元素
             * 表示它是当前最早的元素
             * 唤醒一条leader 进行处理
             */
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally { // 新增无论是否失败都将释放锁
            lock.unlock();
        }
    }

2.5.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止

    public void put(E e) {
        offer(e);
    }

2.5.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理或者操作成功也会释放锁

    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

2.6 DelayQueue 删除业务

2.6.1 第一种删除:操作失败抛异常

    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.remove(o); // 直接调用优先级队列进行删除
        } finally { // 无论删除是否成功都将释放锁
            lock.unlock();
        }
    }

2.6.2 第二种删除:操作失败返回特殊值

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek(); // 获取队列的元素
            /*
             * 如果 first 元素延迟时间大于0,则返回null;
             * 否则就是小于0;表示延迟时间已过时
             * 则调用q.poll() 进行删除
             */
            return (first == null || first.getDelay(NANOSECONDS) > 0)
                ? null
                : q.poll();
        } finally {
            lock.unlock();
        }
    }

2.6.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 实现一个无限循环,直到成功获取到期元素
            for (;;) {
                E first = q.peek(); // 获取队首元素
                // 如果队列为空,则线程陷入阻塞
                if (first == null)
                    available.await();
                // 如果队列不为空的处理
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0L) // 元素的延迟时间已过期,进行删除
                        return q.poll();
            
                    first = null;
                    // 如果元素的延迟时间没有过期,但是有leader在处理其他业务,则进入阻塞
                    if (leader != null)
                        available.await();
                    // 如果元素的延迟时间没有过期,也没有leader在处理其他业务,则进入阻塞
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

2.6.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁

    /**
     * 删除元素,在指定的时间内;
     *
     */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // for 循环是为了在超时时间内反复尝试获取队列元素
            for (;;) {
                E first = q.peek(); // 获取队首元素
                // 处理延迟队列为空的情况
                if (first == null) {
                    // 指定的时间已到,则返回null;表示在超时时间内没有获取到任何元素
                    if (nanos <= 0L) 
                        return null;
                    // 指定的时间未到,将该删除线程进行阻塞等待;并更新nanos
                    else
                        nanos = available.awaitNanos(nanos);

                // 处理延迟队列为不空的情况
                } else {
                    // 获取元素的延迟时间
                    long delay = first.getDelay(NANOSECONDS);
                    // 时间已过期,进行删除
                    if (delay <= 0L) return q.poll();
                    // 指定范围的时间已到,直接返回null;表示在超时时间内没有获取到任何元素
                    if (nanos <= 0L) return null;
                    
                    first = null; 
                    /*有leader进行处理时或者指定时间nanos 小于 延迟时间 ;此时线程进入阻塞状态*/
                    if (nanos < delay || leader != null)
                        nanos = available.awaitNanos(nanos);
            
                    // 否则,当前线程设置为leader,等待delay时间,然后更新nanos值
                    // 其实这一段就是leader的轮流;只有当leader为null时,才将当前线程切换为leader
                    // 之前的leader的线程要么进行阻塞,要么已完成
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            long timeLeft = available.awaitNanos(delay);
                            nanos -= delay - timeLeft;
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally { // 最终是否删除元素成功都将释放锁,并且如果此时队列不为空,并且没有leader进行处理,则唤醒一条leader进行处理
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

2.7 DelayQueue 检索业务

    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return q.peek();
        } finally {
            lock.unlock();
        }
    }

2.8 DelayQueue 扩容机制

        由于DelayQueue是无界队列;所以是有扩容机制的,但是从源码的角度来讲,延迟队列的无界性是根据优先级队列来的,因为延迟队列里面用的就是优先级队列;所以扩容机制用的也是优先级队列的扩容机制

        有关于优先级队列讲解:java-01-基础篇-04 Java集合-03-Queue-CSDN博客

这里面讲解到了优先级队列的实现业务;所以延迟队列的扩容机制实际上就是优先级队列的扩容机制

三,TransferQueue 传输队列

        在上一章讲解到了SynchronousQueue 同步队列;在同步队列里面内置一个Transfer 传输队列;那这个TransferQueue 同步队列接口和SynchronousQueue 同步队列内置的Transfer 传输队列区别在哪里?为什么又要定义一个接口这样子的接口?并且我们从继承图可以得知,这是一个独单的接口。

        说到区别;我们完全可以从上一章讲解到的SynchronousQueue 同步队列与其他队列有什么区别开始入手讲:

        上一章链接: 

        总结就是  SynchronousQueue 同步队列采用直接传输(传输队列)的方式,使得生产者和消费者一对一匹配,消除了队列中的缓冲区。这确保了生产者和消费者的速度严格匹配。而其他传统阻塞队列是通过缓冲的方式来进行存储元素,其生产者与消费者之间的数据处理较慢。

package java.util.concurrent;
/**
 * @since 1.7
 * @author Doug Lea
 * @param <E> the type of elements held in this queue
 */
public interface TransferQueue<E> extends BlockingQueue<E> {

    /**
     * 尝试将元素 e 直接传输给消费者(如果有的话)。
     * 如果当前有消费者在等待,则立即将元素传输给消费者并返回 true。
     * 如果没有消费者在等待,则返回 false,并且不会将元素加入队列。
     * @param e 传入给消费者使用的接口
     */
    boolean tryTransfer(E e);

    /**
     * 将元素 e 直接传输给消费者
     * 如果当前没有消费者在等待,则生产者将被阻塞,直到有消费者接收该元素
     * 这种方法确保传输的元素一定会被消费
     */
    void transfer(E e) throws InterruptedException;

    /**
     * 尝试将元素 e 在指定的超时时间内传输给消费者。
     * 如果在超时时间内有消费者接收该元素,则返回 true。
     * 如果在超时时间内没有消费者接收该元素,则返回 false。
     * 如果超时时间为零,则该方法的行为与 tryTransfer(E e) 类似
     * 
     */
    boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;

    /**
     * 检查是否有消费者在等待元素
     * 如果至少有一个消费者在等待元素,则返回 true
     * 如果没有消费者在等待元素,则返回 false
     */
    boolean hasWaitingConsumer();

    /**
     * 返回当前等待元素的消费者数量。
     * 这个方法对于监控和调试非常有用,可以帮助了解当前系统中消费者的状态。
     */
    int getWaitingConsumerCount();
}

3.1 LinkedTransferQueue 子类

3.2 LinkedTransferQueue 内部类

3.2.1 Node 类

    static final class Node implements ForkJoinPool.ManagedBlocker {/*忽略代码*/}

        从Node的继承结构来看,和 SynchronousQueue同步队列的QNode/SNode 内部类的作用是一样的,都是在节点里面提供用于管理阻塞等待相关业务的实现。这样也是为LinkedTransferQueue 队列提供直接输入功能做准备。实现方式和SynchronousQueue 同步队列是一样的。只不过这个TransferQueue 接口,不仅实现了直接传输还实现阻塞队列的基本操作!

       也就是该队列不仅支持传入和支持存储的方式。这样主要是为了应对生产者在没有消费者时阻塞,确保每个传输的元素都能被及时消费。这些方法在高度同步的生产者-消费者场景中非常有用。特别是 LinkedTransferQueue 实现了这些方法,提供了一种灵活且高效的方式来进行并发编程。

3.2.1.1 Node 构造器
Node() {
    isData = true;
}
Node(Object item) {
    ITEM.set(this, item);
    isData = (item != null);
}
3.2.1.2 Node的属性
    static final class Node implements ForkJoinPool.ManagedBlocker {
        final boolean isData;   // true - 生产者数据 | false - 消费者数据
        volatile Object item;   // 节点内容
        volatile Node next;     // 后置节点
        volatile Thread waiter; // 处理节点的线程(陷入等待状态的线程)
        private static final long serialVersionUID = -3375979862319811754L;
    }
3.2.1.3 Node的业务

        通过CAS设置后置节点

/**
 * 通过CAS设置后置节点
 * @param cmp 比较节点(旧的后置节点)
 * @param val 要设置新的后置节点
 */
final boolean casNext(Node cmp, Node val) {
    return NEXT.compareAndSet(this, cmp, val);
}

        通过CAS设置节点内容

/**
 * 通过CAS设置节点内容
 * @param cmp 比较节点内容(旧内容)
 * @param val 要设置新的节点内容
 */
final boolean casItem(Object cmp, Object val) {
    return ITEM.compareAndSet(this, cmp, val);
}

         删除Node的后置节点;删除的方式是自我连接,之后通过GC垃圾回收处理

/** 通过CAS将后置节点进行自我连接,给GC垃圾回收进行处理  */
final void selfLink() {
    NEXT.setRelease(this, this);
}

        将当前节点推进为后置节点

final void appendRelaxed(LinkedTransferQueue.Node next) {
    NEXT.setOpaque(this, next);
}

        尝试匹配节点内容

/**
 * 尝试匹配节点内容,如果节点内容匹配成功,则返回true;
 * @param cmp 比较内容
 * @param val 新内容
 */
final boolean tryMatch(Object cmp, Object val) {
    if (casItem(cmp, val)) {
        LockSupport.unpark(waiter);
        return true;
    }
    return false;
}

         如果无法将具有给定模式的节点附加到此节点,因为此节点不匹配并且具有相反的数据模式,则返回true

final boolean cannotPrecede(boolean haveData) {
    boolean d = isData;
    return d != haveData && d != (item == null);
}
   /**
    * 此方法用于执行阻塞操作
    * 返回true表示阻塞操作已完成
    */
    public final boolean block() {
        while (!isReleasable()) LockSupport.park();
        return true;
    }

    /**
     * 此方法用于检查阻塞操作是否已经可以解除
     * 返回 true 表示可以解除阻塞
     */
    public final boolean isReleasable() {
        return (isData == (item == null)) ||
            Thread.currentThread().isInterrupted();
    }

3.3 LinkedTransferQueue 属性

package java.util.concurrent;
public class LinkedTransferQueue<E> extends AbstractQueue<E>
    implements TransferQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -3223113410248163686L;
    /*
     * 这个常量定义了自旋等待的阈值,用于控制在某些情况下是否进行忙等待(自旋)。
     * 当等待超时时间低于这个阈值时,线程会通过自旋等待而不是挂起,
     * 以减少上下文切换的开销,从而提高性能。
     */
    static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1023L;
    /*
     * 这个常量定义了清理操作的阈值。
     * 当队列中未处理的节点数量超过这个阈值时,
     * 会触发清理操作,以移除无效或取消的节点,防止队列膨胀。
     */
    static final int SWEEP_THRESHOLD = 32;

    /*
     * 这是一个指向队列头节点的指针。
     * 使用 volatile 修饰,确保对 head 的修改对于所有线程可见。
     * transient 修饰符表明在序列化过程中不包含这个字段
     */
    transient volatile Node head;
    
    /*
     * 这是一个指向队列尾节点的指针。
     * 使用 volatile 修饰,确保对 tail 的修改对于所有线程可见
     */
    private transient volatile Node tail;
    /*
     * 这个布尔变量指示是否需要进行队列清理操作。
     * 当队列中存在过多的无效节点时,这个标志位会被设置为 true,以提示进行清理。
     */ 
    private transient volatile boolean needSweep;
    /* 忽略代码 */
    // 表示立即执行的操作,如无超时的 poll 和 tryTransfer
    private static final int NOW   = 0; 
    // 表示异步操作,如 offer、put 和 add
    private static final int ASYNC = 1;
    // 表示同步操作,如 transfer 和 take。 这些操作会阻塞,直到成功完成 
    private static final int SYNC  = 2;
    // 表示带有超时的操作,如带超时的 poll 和 tryTransfer
    private static final int TIMED = 3; 
}

        LinkedTransferQueue 利用这些属性和常量来实现高效的并发数据传输和管理。SPIN_FOR_TIMEOUT_THRESHOLD 用于优化短时等待的性能,SWEEP_THRESHOLD 用于维护队列的清洁和效率。
head 和 tail 指针用于维护队列的结构,支持并发访问。
needSweep 标志位用于指示何时需要清理无效节点,防止队列过度膨胀。
这些操作类型常量(NOW、ASYNC、SYNC、TIMED)简化了内部方法的调用逻辑,有助于明确不同操作模式下的行为。 

3.4 LinkedTransferQueue 新增业务

3.4.1 第一种新增:操作失败抛异常

    public boolean add(E e) {
        xfer(e, true, ASYNC, 0L);
        return true;
    }

3.4.2 第二种新增:操作失败返回特殊值

    public boolean offer(E e) {
        xfer(e, true, ASYNC, 0L);
        return true;
    }

3.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止

    public void put(E e) {
        xfer(e, true, ASYNC, 0L);
    }

3.4.4  第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理或者操作成功也会释放锁

    public boolean offer(E e, long timeout, TimeUnit unit) {
        xfer(e, true, ASYNC, 0L);
        return true;
    }
    

        总结:所有队列新增的接口方式都是调用 xfer()方法,该方法就是传输队列里面的传输方法业务。

3.5 LinkedTransferQueue 删除业务

3.5.1 第一种删除:操作失败抛异常

    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

3.5.2 第二种删除:操作失败返回特殊值

    public E poll() {
        return xfer(null, false, NOW, 0L);
    }

3.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止

    public E take() throws InterruptedException {
        E e = xfer(null, false, SYNC, 0L);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

3.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E e = xfer(null, false, TIMED, unit.toNanos(timeout));
        if (e != null || !Thread.interrupted())
            return e;
        throw new InterruptedException();
    }

        总结:删除业务方法也是调用xfer()方法进行传输,在删除业务里面是属于消费者的类型;而新增是属于生产者的类型。

3.7 LinkedTransferQueue 传输业务

  xfer 方法是 LinkedTransferQueue 中的核心方法之一,用于处理元素的传输。它既可以用于将数据放入队列(传输),也可以用于从队列中获取数据。此方法支持同步和异步传输,并且可以处理带有超时的操作。

    /**
     * xfer 方法是 LinkedTransferQueue 中的核心方法之一,用于在生产者和消费者之间传递元素。
     * 它实现了同步队列的核心逻辑,确保只有当消费者准备好接收数据时,生产者才能成功放入数据,反之亦然。
     * 这个方法处理了多种场景,包括立即返回、异步操作和带超时的操作。
     * @param e 要传输的元素
     * @param haveData true - 生产者 | false - 消费者
     * @param how 操作类型(NOW(立即返回)、ASYNC(异步操作)、SYNC(同步操作)或 TIMED(带超时的操作)。)
     * @param nanos 超时时间(纳秒)
     */
    private E xfer(E e, boolean haveData, int how, long nanos) {
        if (haveData && (e == null)) // true 表示生产者,却没有元素传入,抛空指针异常
            throw new NullPointerException();

        /*
         * 使用 restart 标签标记外层循环的起点。
         * 这个循环在成功传输数据或发生特定条件(如超时)前一直运行。
         */
        restart: for (Node s = null, t = null, h = null;;) {
            // 根据节点的类型(生产者或消费者)选择起始节点。
            /*
             * p 节点的初始值根据当前尾节点(tail)或头节点(head)确定。
             *
             */
            for (Node p = ( t != (t = tail) && t.isData == haveData ) ? t : (h = head);;) {
                final Node q; final Object item;
                /*
                 * p.isData - p 节点的数据类型(生产者数据节点或消费者请求节点)
                 * haveData - 当前操作类型(生产者或消费者)
                 * 如果 p 节点的类型与当前操作(生产者或消费者)不匹配,
                 * 并且 haveData 的值与 p.item 是否为空相匹配,
                   则尝试通过tryMatch匹配该节点。如果匹配成功唤醒一条线程
                 * 
                 */
                if (p.isData != haveData && 
                    haveData == ((item = p.item) == null)) {
                    if (h == null) h = head;
                    if (p.tryMatch(item, e)) {
                        if (h != p) skipDeadNodesNearHead(h, p);
                        return (E) item;
                    }
                }
                // 如果 p 节点的下一个节点 q 为 null,表示到达了队列的末尾
                if ((q = p.next) == null) {
                    // 如果操作类型为 NOW,立即返回元素 e
                    if (how == NOW) return e; 
                    if (s == null) s = new Node(e);
                    // 尝试将新节点 s 作为当前节点 p 的下一个节点(使用 casNext 方法)
                    if (!p.casNext(null, s)) continue;
                    // 如果当前节点 p 不是尾节点 t,更新尾节点为新节点 s。
                    if (p != t) casTail(t, s);
                    // 如果操作类型为 ASYNC,立即返回元素 e。
                    if (how == ASYNC) return e;
                    // 否则,调用 awaitMatch 方法等待匹配,传入新节点 s、当前节点 p、元素 e、是否带超时的标志 TIMED 和超时时间 nanos。
                    return awaitMatch(s, p, e, (how == TIMED), nanos);
                }
                if (p == (p = q)) continue restart;
            }
        }
    }

        通过这样设计,xfer 方法实现了生产者和消费者之间的同步传输机制,确保数据只有在消费者准备好接收时才能被生产者传输。       

        尝试在指定时间内进行匹配

    /**
     * awaitMatch 方法是 LinkedTransferQueue 中用于等待节点匹配的方法。该方法实现了线程在节点匹配过程中等待的逻辑,并处理超时和中断等情况。     
     * @param s     当前接待你
     * @param pred  当前节点的前置节点
     * @param e     当前元素
     * @param timed 是否有超时
     * @param nanos 超时时间
     */
    private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
        final boolean isData = s.isData;
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        final Thread w = Thread.currentThread();
        int stat = -1;                   // -1: may yield, +1: park, else 0
        Object item;
        /*
         * 循环如果当前节点的元素仍然是 e,
         * 继续循环匹配处理机制;
         * 通过一个复杂的等待逻辑,用于在生产者和消费者之间进行匹配。它处理了如下情况:
         * 【超时和中断】确保在这些情况下正确取消节点并将其从队列中移除
         * 【让步和挂起】根据当前状态和上下文决定是否让步或挂起当前线程
         * 【匹配成功】如果匹配成功,返回匹配的元素
         */
        while ((item = s.item) == e) {
            // 如果需要清理,调用 sweep 方法进行清理
            if (needSweep) sweep();
            // 如果超时或线程被中断,尝试取消当前节点。
            else if ((timed && nanos <= 0L) || w.isInterrupted()) {
                if (s.casItem(e, (e == null) ? s : null)) {
                    unsplice(pred, s);   // cancelled
                    return e;
                }
            }
            // 如果状态为 -1 或 0,尝试让步或挂起当前线程
            else if (stat <= 0) {
                if (pred != null && pred.next == s) {
                    if (stat < 0 &&
                        (pred.isData != isData || pred.isMatched())) {
                        stat = 0;        // yield once if first
                        Thread.yield();
                    }
                    else {
                        stat = 1;
                        s.waiter = w;    // enable unpark
                    }
                }                        // else signal in progress
            }
            // 如果当前节点的元素不再是 e,退出循环。说明已经匹配到了
            else if ((item = s.item) != e) break;                 
            // 如果此时还没有超时
            else if (!timed) {
                LockSupport.setCurrentBlocker(this); // 设置当前线程的阻塞对象
                try {
                    // 调用 ForkJoinPool 的 managedBlock 方法阻塞当前线程,等待匹配。
                    ForkJoinPool.managedBlock(s); 
                } catch (InterruptedException cannotHappen) { }
                // 清除当前线程的阻塞对象
                LockSupport.setCurrentBlocker(null);
            }
            // 如果有超时
            else {
                nanos = deadline - System.nanoTime(); // 计算剩余的超时时间。
                // 如果剩余时间超过阈值,挂起当前线程指定的时间
                if (nanos > SPIN_FOR_TIMEOUT_THRESHOLD)
                    LockSupport.parkNanos(this, nanos);
            }
        }
        // 如果状态为 1,清除当前节点的等待者
        if (stat == 1) WAITER.set(s, null);
        // 如果当前节点不是数据节点,将其元素设置为自身,避免垃圾回收。
        if (!isData) ITEM.set(s, s);              // self-link to avoid garbage
        return (E) item;
    }

3.8 LinkedTransferQueue 其他业务

3.8.1 获取第一个节点

    final Node firstDataNode() {
        Node first = null;
        restartFromHead: for (;;) {
            Node h = head, p = h;
            while (p != null) {
                if (p.item != null) {
                    if (p.isData) {
                        first = p;
                        break;
                    }
                }
                else if (!p.isData)
                    break;
                final Node q;
                if ((q = p.next) == null)
                    break;
                if (p == (p = q))
                    continue restartFromHead;
            }
            if (p != h && casHead(h, p))
                h.selfLink();
            return first;
        }
    }

3.8.2 统计模型中不匹配的节点

        遍历给定模式中不匹配的节点并对其计数。 由方法size和getWaitingConsumerCount使用

    private int countOfMode(boolean data) {
        restartFromHead: for (;;) {
            int count = 0;
            for (Node p = head; p != null;) {
                if (!p.isMatched()) {
                    if (p.isData != data)
                        return 0;
                    if (++count == Integer.MAX_VALUE)
                        break;  // @see Collection.size()
                }
                if (p == (p = p.next))
                    continue restartFromHead;
            }
            return count;
        }
    }

 3.8.3 CAS更新业务       

        【更新尾部节点】如果传入的cmp 节点,是当前节点的尾部节点,就通过CAS更新尾部节点

    private boolean casTail(Node cmp, Node val) {
        return TAIL.compareAndSet(this, cmp, val);
    }

        【更新头部节点】如果传入的cmp 节点,是当前节点的头部节点,就通过CAS更新头部节点

    private boolean casHead(Node cmp, Node val) {
        return HEAD.compareAndSet(this, cmp, val);
    }

        【更新前置节点】如果传入的cmp 节点,是当前节点的前置节点,就通过CAS更新前置节点

    private boolean tryCasSuccessor(Node pred, Node c, Node p) {
        if (pred != null)
            return pred.casNext(c, p);
        if (casHead(c, p)) {
            c.selfLink();
            return true;
        }
        return false;
    }

  3.8.4 跳过已匹配的节点(即“死节点”)来保持队列的连贯性和高效性

        这两个方法 skipDeadNodesskipDeadNodesNearHead 用于 LinkedTransferQueue 中清理已匹配的(也称为“死”)节点,以维护队列的效率和防止内存泄漏。它们通过在队列中跳过这些无效节点(已死的节点)来保持队列的连贯性和流畅性

    /**
     * @param pred 最后一个已知的有效节点,或者如果没有则为 null
     * @param c    第一个死节点
     * @param p    最后一个死节点
     * @param q    下一个有效节点,或者如果在末尾则为null
     */
    private Node skipDeadNodes(Node pred, Node c, Node p, Node q) {
        /* 
         * 下面四种断言案例就是节点已死的状态
         * 确保 pred 不等于 c,p 不等于 q,并且 c 和 p 都已经匹配(即是死节点)
         */
        // assert pred != c;
        // assert p != q;
        // assert c.isMatched();
        // assert p.isMatched();
        // 处理 q 为空的情况
        if (q == null) { // 如果 q 为 null,表示 p 是队列的末尾节点
            // 如果 c 等于 p,返回 pred。否则,将 q 设置为 p
            if (c == p) return pred;
            q = p;
        }
        // 尝试 CAS 操作调用 tryCasSuccessor(pred, c, q) 尝试将 c 的后继节点从 c 更改为 q。
        return (tryCasSuccessor(pred, c, q) && (pred == null || !pred.isMatched()))
            ? pred : p;
    }

    /**
     * 这个方法用于从队列头部删除连续的死节点
     * h 曾经是头节点的节点
     * p 死节点链的最后一个节点
     */
    private void skipDeadNodesNearHead(Node h, Node p) {
        /*确保 h 和 p 不为 null,并且 h 不等于 p,并且 p 已经匹配(即是死节点)*/
        // assert h != null;
        // assert h != p;
        // assert p.isMatched();
        // 跳过连续的死节点
        for (;;) { //使用一个无限循环来遍历从 h 到 p 的节点
            final Node q; 
            if ((q = p.next) == null) break;
            else if (!q.isMatched()) { p = q; break; }
            else if (p == (p = q)) return;
        }
        // CAS 操作设置新的头节点;如果成功,将旧头节点 h 设置为自引用,以避免垃圾回收。
        if (casHead(h, p)) h.selfLink();
    }

       BlockingQueue 单向队列下图的所有继承结构类都大概分析了一遍;BlockingQueue 阻塞队列分别写了三篇,讲一下的子类都讲解了一遍。

        各位读者大佬发现小编有错误的地方和理解不对的地方,欢迎提出!如下是单向队列的全部文章链接:

java-01-基础篇-04 Java集合-03-Queue-CSDN博客

java-01-基础篇-04 Java集合-03-BlockingQueue(一)-CSDN博客

java-01-基础篇-04 Java集合-03-BlockingQueue(二)-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值