去年面试的时候,面试官问我怎样用java的concurrent包的lock实现线程的生产消费模型。当时我一时语塞,描述的比较模糊。
因此也就花了时间看了下java的BlockingQueue的实现,然后自己手写了一下。收获颇丰
谈到生产消费模型,必然意味着竞争公共资源。因此就会涉及到锁,生产消费都是在某个边界条件触发的时候叫停,这就是ReentrantLock/Object的通知挂起机制的根由。
package io.freebird.queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 阻塞队列容器的数组版简单实现 * * @author freebird * @date 2019/6/24 16:06 */ public class BlockingQueue<T> { /** * 容器 */ private final Object[] items; /** * 容器的大小 */ private int size = 0; private final Lock lock = new ReentrantLock(true); /** * 队列不空的条件 */ private final Condition notEmpty = lock.newCondition(); /** * 队列不满的条件 */ private final Condition notFull = lock.newCondition(); public BlockingQueue(int capacity) { this.items = new Object[capacity]; } /** * 保证进队的有序性 * 以及整个队列元素的线程安全性 * 多余的线程一直挂着占用资源,因此应该适当的时间将其释放 * * @param t 进队元素 */ public void enter(T t) throws InterruptedException { if (t == null) { throw new NullPointerException("the enter element cannot be null."); } lock.lockInterruptibly(); try { while (size == items.length) { System.out.println("队列满了..."); notFull.await(); } items[size++] = t; // 队列新增了元素,唤醒notEmpty中挂起的线程. System.out.println("我要入队..."); notEmpty.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public T poll() throws InterruptedException { lock.lockInterruptibly(); T ret = null; final T tail = (T) items[items.length - 1]; try { while (size == 0) { // 挂起当前线程.等待唤醒.一直挂起很浪费资源.所以应该挂起一定的时间就中断线程. System.out.println("队列空了..."); notEmpty.await(); } ret = (T) items[0]; for (int i = 0; i < size; i++) { if (size == items.length) { items[i] = i + 1 == size ? tail : items[i + 1]; } else { items[i] = items[i + 1]; } } items[--size] = null; System.out.println("我要出队..."); notFull.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } return ret; } }
演示的demo如下:
package io.freebird.queue.demo; import io.freebird.queue.BlockingQueue; import lombok.SneakyThrows; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author zhouziyan * @date 2019/6/24 16:24 */ public class BlockingQueueDemo { public static void main(String[] args) { final BlockingQueue queue = new BlockingQueue(10); ExecutorService enterThreadPool = Executors.newFixedThreadPool(10); ExecutorService pollThreadPool = Executors.newFixedThreadPool(10); for (int i = 0; i < 11; i++) { final int finalI = i; enterThreadPool.execute(new Runnable() { public void run() { try { queue.enter(finalI); } catch (InterruptedException e) { } } }); pollThreadPool.execute(new Runnable() { @Override public void run() { try { queue.poll(); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }
输出结果为:
我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队... 我要入队... 我要出队...
可以看出是很有序的,在线程竞争同一个资源中,一定要用同一把锁哦