数组实现队列,阻塞队列

今天做了一个笔试题,使用数组实现一个队列。现在记录一下当时的实现,之后再对这个实现进行改造升级。


/**
 * @program: read-write
 * @description: 数组实现队列
 * @author: WangJie
 * @create: 2019-03-03 21:45
 **/
public class MyQueue<T> {
    private Object[] queue;
    private int in;
    private int out;
    private int size;
    private int length;

    MyQueue(int size) {
        queue = new Object[size];
        in = out = length = 0;
        this.size = size;
    }

    public boolean offerLast(T obj) {
        if (in == out && queue[in] != null) {
            return false;
        }
        queue[in] = obj;
        in = (in + 1) % size;
        return true;
    }

    public T pollFirst() {
        T target = get(out);
        queue[out] = null;
        if (in == out && target == null) {
            throw new RuntimeException("当前队列无元素");
        }
        out = (out+1)%size;
        return target;

    }

    private T get(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException();
        }
        return (T) queue[index];
    }

    public static void main(String[] args) {
        MyQueue<String> queue = new MyQueue<>(3);
        queue.offerLast("a");
        queue.offerLast("b");
        queue.offerLast("c");
        queue.offerLast("d");
        System.out.println(queue.pollFirst());
        System.out.println(queue.pollFirst());
        System.out.println(queue.pollFirst());
        System.out.println(queue.pollFirst());


    }
}

仔细想想,上面代码有些问题。一是没有加锁同步,那么这个队列是无法在多线程环境下使用的,二是入队会返回true或false,而出队时如果队列已经为空,则会抛出异常(当然也可以返回null),出队入队都需要使用该队列的客户端去检查结果,并作出相应的处理,当队列已满,客户端可能会使用忙等待,循环入队直到入队成功,出队也是如此,这会消耗大量CPU时间。所以,应该由队列提供出队入队的阻塞功能。
修改后的阻塞队列代码如下:

	public synchronized boolean offerLast(T obj) throws InterruptedException {
        while (in == out && queue[in] != null) {
            wait();
        }
        queue[in] = obj;
        in = (in + 1) % size;
        notifyAll();
        return true;
    }

    public synchronized T pollFirst() throws InterruptedException {

        while (in == out && target == null) {
            wait();
        }
        T target = get(out);
        queue[out] = null;
        out = (out+1)%size;
        notifyAll();
        return target;

    }

以下是用来测试阻塞队列的代码:

@Test
 public void blockingQueueTest() throws InterruptedException {
     MyBlockingArrayQueue<Integer> queue = new MyBlockingArrayQueue<>(5);
     Runnable provider = new Runnable() {
         private int a =0;
         @Override
         public void run() {
             while (true) {
                 try {
                     queue.offerLast(a);
                     System.out.println("生产者添加了:"+a);
                     a++;
                 } catch (InterruptedException e) {
                     System.out.println("生产终止");
                     break;
                 }
             }
         }
     };

     Runnable consumer1 =()->{
         while (true){
             try {
                 Integer number = queue.pollFirst();
                 System.out.println("1号消费者消费:"+number);
                 Thread.sleep(1000L);
             } catch (InterruptedException e) {
                 System.out.println("1号消费者终止");
                 break;
             }
         }
     };
     Runnable consumer2 =()->{
         while (true){
             try {
                 Integer number = queue.pollFirst();
                 System.out.println("2号消费者消费:"+number);
                 Thread.sleep(1000L);
             } catch (InterruptedException e) {
                 System.out.println("2号消费者终止");
                 break;
             }
         }
     };
     ExecutorService executorService = Executors.newCachedThreadPool();
     Future<?> providerFuture = executorService.submit(provider);
     Future<?> consumer1Future = executorService.submit(consumer1);
     Future<?> consumer2Future = executorService.submit(consumer2);
     Thread.sleep(20000L);
     providerFuture.cancel(true);
     consumer1Future.cancel(true);
     consumer2Future.cancel(true);
 }

以上代码还是有些可以改进的地方,notifyAll会唤醒所有在该阻塞队列上等待的消费者和生产者,而当他们醒来时可能会发现自己需要的条件并不满足,比如对于一个已满队列,消费者在消费一个后调用notifyAll,本意是提醒某个生产者可以再生产一个了,但是他提醒了所有的生产者和消费者,只有一个生产者会满足条件进行生产,而其他生产者和所有消费者醒来后都重新进入等待。这一过程中会发生锁争用,以及线程上下文的切换,占用CPU时间。如果将notifyAll改成notify。

使用wait和notifyAll带来问题的主要原因是所有的线程都会在这这一个条件队列上等待,这就导致消费者要想将可以继续生产了的信号传递给生产者,那么他必须notifyAll,如果使用notify,那么可能会被另一个消费者线程收到,其他线程就收不到了,之后就造成了生产者消费者的全部阻塞。所以使用多个条件队列,让线程在各自的条件上等待,那么就不需要唤醒所有线程。这一实现可以通过Lock的Condition来实现。

  private ReentrantLock lock = new ReentrantLock();
  private Condition inCondition = lock.newCondition();
  private Condition outCondition = lock.newCondition();

public  boolean offerLast(T obj) throws InterruptedException {
      try {
          lock.lock();
          while (in == out && queue[in] != null) {
              inCondition.await();
          }
          queue[in] = obj;
          in = (in + 1) % size;
          outCondition.signal();
          return true;
      }finally {
          lock.unlock();
      }
  }

  public  T pollFirst() throws InterruptedException {
      try {
          lock.lock();
          while (in == out && get(out) == null) {
              outCondition.await();
          }
          T target = get(out);
          queue[out] = null;
          out = (out + 1) % size;
          inCondition.signal();
          return target;
      }finally {
          lock.unlock();
      }
  }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值