详解Java中的ArrayBlockingQueue

ArrayBlockingQueue 概述

ArrayBlockingQueue 是 Java 中 java.util.concurrent 包下的一个阻塞队列实现,底层基于数组,是线程安全的。它是一个 有界队列,需要在创建时指定容量大小。此类的主要特性包括:

  1. 线程安全:

    • 使用 独占锁(ReentrantLock)条件变量 来实现线程安全的操作。

  2. 先进先出(FIFO):

    • 元素按插入顺序排列,头部是最先插入的元素,尾部是最后插入的元素。

  3. 有界性:

    • 队列容量固定,不能超过初始化时指定的大小。


核心构造方法

1
public ArrayBlockingQueue(int capacity)
  • 创建一个指定容量的队列。

1
public ArrayBlockingQueue(int capacity, boolean fair)
  • fair: 决定锁的公平性,true 表示公平锁,false 为非公平锁(性能更高)。

1
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
  • 初始化队列,并将给定集合中的所有元素按迭代顺序添加到队列。


核心字段

1
2
3
4
5
6
7
8
private final Object[] items; // 存储队列元素的数组
private int takeIndex;        // 指向队列头部(下一个被取出的元素)
private int putIndex;         // 指向队列尾部(下一个插入的位置)
private int count;            // 队列中当前的元素个数
private final ReentrantLock lock;    // 独占锁,保证线程安全
private final Condition notEmpty;   // 条件变量:队列不为空的等待条件
private final Condition notFull;    // 条件变量:队列未满的等待条件

关键方法解读

1. offer(E e)

尝试向队列中插入元素(非阻塞方法)。如果队列已满,返回 false

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();  // 获取锁
    try {
        if (count == items.length) // 队列已满
            return false;
        enqueue(e);  // 插入元素
        return true;
    } finally {
        lock.unlock();  // 释放锁
    }
}

2. put(E e)

向队列中插入元素,如果队列已满,当前线程阻塞直到队列有空间。

1
2
3
4
5
6
7
8
9
10
11
12
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();  // 可中断获取锁
    try {
        while (count == items.length)  // 队列已满,等待 notFull 信号
            notFull.await();
        enqueue(e);  // 插入元素
    } finally {
        lock.unlock();  // 释放锁
    }
}

3. take()

从队列中取出一个元素,如果队列为空,当前线程阻塞直到有可用元素。

1
2
3
4
5
6
7
8
9
10
11
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();  // 可中断获取锁
    try {
        while (count == 0// 队列为空,等待 notEmpty 信号
            notEmpty.await();
        return dequeue();  // 获取队头元素
    } finally {
        lock.unlock();  // 释放锁
    }
}

4. peek()

非阻塞方法,返回队列头部元素但不移除。如果队列为空,返回 null

1
2
3
4
5
6
7
8
9
public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();  // 获取锁
    try {
        return (count == 0) ? null : itemAt(takeIndex);  // 获取头部元素
    } finally {
        lock.unlock();  // 释放锁
    }
}

底层核心方法

1. enqueue(E e)

向队列尾部插入元素。

1
2
3
4
5
6
7
8
private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;  // 在 putIndex 位置插入元素
    if (++putIndex == items.length)  // 如果到达数组尾部,回到起始位置(循环数组)
        putIndex = 0;
    count++;  // 更新队列元素个数
    notEmpty.signal();  // 唤醒等待 notEmpty 的线程
}

2. dequeue()

从队列头部取出元素。

1
2
3
4
5
6
7
8
9
10
11
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];  // 获取队列头部的元素
    items[takeIndex] = null// 释放队列头部的引用
    if (++takeIndex == items.length)  // 如果到达数组尾部,回到起始位置
        takeIndex = 0;
    count--;  // 更新队列元素个数
    notFull.signal();  // 唤醒等待 notFull 的线程
    return x;
}

线程安全性

  • 使用 独占锁 来控制对队列的并发访问。

  • 条件变量 notEmptynotFull 分别控制取元素和插入元素的等待逻辑。

  • 锁的公平性(可选)决定线程获取锁的顺序,公平锁更公平但性能稍差。


应用场景

  1. 生产者-消费者模型

    • 生产者线程向队列插入元素,消费者线程从队列取出元素。

  2. 任务调度

    • 用于线程池中存储和分发任务。

  3. 流量控制

    • 通过有界性限制队列中的元素数量,防止内存溢出。


示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) {
    ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
    // 生产者线程
    new Thread(() -> {
        try {
            for (int i = 1; i <= 10; i++) {
                queue.put(i);
                System.out.println("Produced: " + i);
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
    // 消费者线程
    new Thread(() -> {
        try {
            while (true) {
                Integer value = queue.take();
                System.out.println("Consumed: " + value);
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

运行结果会显示生产者和消费者交替操作,遵循 FIFO 原则。

posted @   luorx  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
点击右上角即可分享
微信分享提示