阻塞队列(JAVA)

本文详细介绍了阻塞队列的概念、Java中的BlockingQueue接口及其使用,以及模拟实现一个线程安全的阻塞队列,包括队列满和队列空时的阻塞逻辑以及wait()和notify()的配合。
摘要由CSDN通过智能技术生成

阻塞队列是一种特殊的队列,也遵守 "先进先出" 的原则。

阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素;
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素。

 JAVA标准库中已经实现了阻塞队列,我们可以直接进行使用

BlockingQueue

BlockingQueue是一个接口,阻塞队列也和普通队列一样有两种实现方式:数组和链表。

注:创建阻塞队列时需要传入队列的长度参数。

BlockingQueue<String> queue = new ArrayBlockingQueue(10);

由于 BlockingQueue继承自Queue所以普通队列的接口也可以正常使用,但是没有阻塞效果。

BlockingQueue提供了两个带有阻塞效果且线程安全的方法:put()和take()。

public static void main(String[] args) throws InterruptedException {
    //创建一个长度为10的阻塞队列
    BlockingQueue<String> queue = new ArrayBlockingQueue(10);
    //入队五次
    queue.put("1");
    queue.put("2");
    queue.put("3");
    queue.put("4");
    queue.put("5");
    //出队列六次
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    //由于此时队列为空,所以会出现阻塞
    System.out.println(queue.take());
}

为了更好的理解阻塞队列我们可以自己设计一个简单的阻塞队列。

模拟实现

先写一个普通的循环队列

public class MyBlockingQueue {
    private String[] queue = new String[10];
    //表示队列内元素数量
    private int size;
    //头尾指针
    private int head;
    private int tail;
    //入队列
    public boolean put(String str) {
        if (this.size == 10) {
            //队列满
            return false;
        }
        this.queue[this.tail++] = str;
        this.tail %= 10;
        this.size++;
        return true;
    }
    //出队列
    public String take() {
        if (this.size == 0) {
            //队列空
            return null;
        }
        String ret = this.queue[this.head];
        this.head = (++this.head+10) % 10;
        this.size--;
        return ret;
    }
}

现在它是线程不安全的所以我们应该加锁,因为里面的两个方法几乎每一步都有修改操作所以我们直接给整个方法都加上锁

//入队列
public synchronized boolean put(String str) {
    ……
}
//出队列
public synchronized String take() {
    ……
}

为了防止编译器优化我们对值会被修改的属性都使用volatile进行修饰

public class MyBlockingQueue {
    private String[] queue = new String[10];
    //表示队列内元素数量
    private volatile int size;
    //头尾指针
    private volatile int head;
    private volatile int tail;
}

接下来我们还需加上阻塞的特性即:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素;
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素。

我们只需在put()方法判断队列满之后将返回修改为等待即可。

//入队列
public synchronized boolean put(String str) {
    if (this.size == 10) {
        //队列满
        try {
            this.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    this.queue[this.tail++] = str;
    this.tail %= 10;
    this.size++;
    return true;
}

当任意线程调用take()方法后put()方法就应该继续执行入队操作,所以在tack方法的最后应该加上notify()方法来唤醒线程。

//出队列
public synchronized String take() {
    if (this.size == 0) {
        //队列空
        return null;
    }
    String ret = this.queue[this.head];
    this.head = (++this.head+10) % 10;
    this.size--;
    this.notify();
    return ret;
}

出队列的阻塞也和入队列的阻塞原理相同。

//入队列
public synchronized boolean put(String str) {
    if (this.size == 10) {
        //队列满
        try {
            this.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    this.queue[this.tail++] = str;
    this.tail %= 10;
    this.size++;
    this.notify();
    return true;
}
//出队列
public synchronized String take() {
    if (this.size == 0) {
        //队列空
        try {
            this.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    String ret = this.queue[this.head];
    this.head = (++this.head+10) % 10;
    this.size--;
    this.notify();
    return ret;
}

wait()和notify()的对应关系如下:

此时代码还是有一个非常隐蔽的BUG。那就是wait()除了可以被notify()唤醒外还可以被 interrupt唤醒所以应该将if判断改为while循环。

//入队列
public synchronized boolean put(String str) {
    while (this.size == 10) {
        //队列满
        try {
            this.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    ……
}
//出队列
public synchronized String take() {
    while (this.size == 0) {
        //队列空
        try {
            this.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    ……
}

测试

public static void main(String[] args) {
    MyBlockingQueue queue = new MyBlockingQueue();
    //入队五次
    queue.put("1");
    queue.put("2");
    queue.put("3");
    queue.put("4");
    queue.put("5");
    //出队列六次
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());
    //由于此时队列为空,所以会出现阻塞
    System.out.println(queue.take());
}

public static void main(String[] args) {
    MyBlockingQueue queue = new MyBlockingQueue();
    //入队11次
    queue.put("1");
    queue.put("2");
    queue.put("3");
    queue.put("4");
    queue.put("5");
    queue.put("6");
    queue.put("7");
    queue.put("8");
    queue.put("9");
    queue.put("10");
    //由于队列满出现阻塞
    queue.put("11");
}

在 jconsole 中查看线程状态为WAITING

完整代码 

public class MyBlockingQueue {
    private String[] queue = new String[10];
    //表示队列内元素数量
    private volatile int size;
    //头尾指针
    private volatile int head;
    private volatile int tail;
    //入队列
    public synchronized boolean put(String str) {
        while (this.size == 10) {
            //队列满
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.queue[this.tail++] = str;
        this.tail %= 10;
        this.size++;
        this.notify();
        return true;
    }
    //出队列
    public synchronized String take() {
        while (this.size == 0) {
            //队列空
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        String ret = this.queue[this.head];
        this.head = (++this.head+10) % 10;
        this.size--;
        this.notify();
        return ret;
    }
}

  • 34
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
Sure, I can help you with that. Here's the Java code for implementing a blocking queue using Jedis: ```java import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.exceptions.JedisDataException; public class JedisBlockingQueue<T> { private final JedisPool jedisPool; private final String queueName; private final int timeout; public JedisBlockingQueue(JedisPool jedisPool, String queueName, int timeout) { this.jedisPool = jedisPool; this.queueName = queueName; this.timeout = timeout; } public void enqueue(T item) { try (Jedis jedis = jedisPool.getResource()) { jedis.rpush(queueName, item.toString()); } } public T dequeue() throws InterruptedException { try (Jedis jedis = jedisPool.getResource()) { while (true) { // Perform a BLPOP operation with the configured timeout // This operation will block until an item is available in the queue // or until the timeout is reached java.util.List<String> result = jedis.blpop(timeout, queueName); // Check if the result is null, which means the operation timed out if (result == null) { continue; } // Extract the item from the result String item = result.get(1); // Handle the case where the item is null or invalid if (item == null) { continue; } // Successfully extracted the item from the queue, return it return (T) item; } } catch (JedisDataException e) { // The BLPOP operation will throw a JedisDataException if the connection is closed // This can happen if another client closes the connection, or if the Redis server // shuts down Thread.sleep(1000); return null; } } } ``` Note that this implementation assumes that the items being stored in the queue are strings (hence the use of `item.toString()` when enqueuing), but you can modify it to handle other types if needed. Also, the `timeout` parameter specifies the maximum amount of time (in seconds) to block when waiting for an item to become available in the queue. Hope this helps!
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值