LinkedList封装实现阻塞队列功能——Condition

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lizhengwei1989/article/details/79437669

有的同学会很疑惑,为什么要自己封装一个阻塞队列呢,java不是有提供现成的阻塞队列吗。当然,如果我们仅仅是使用,完全用java提供的就可以了。之所以封装一个,只是为了更深刻的了解线程同步和锁的使用。也是为了记录一下自己犯的错误。
假如我们的目的是:使用LinkedList实现一个类似于阻塞队列的功能,put添加,超出最大值阻塞,take取出,没有时阻塞
我最初的想法是使用synchronize来做,下面是我第一次实现的代码:

public class LinkedListWrapper<E> {

    private Object lock = new Object();

    private final int max = 10;
    private int num = 0;

    private LinkedList<E> list = new LinkedList<>();

    public void put(E e) {
        synchronized (lock) {
            while (num == max) {
                lock.notifyAll();
                System.out.println("max");
                try {
                    lock.wait();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            list.addLast(e);
            num++;
            lock.notifyAll();
            System.out.println("notmax");
        }
    }

    public E take() {
        synchronized (lock) {
            while (num == 0) {
                lock.notifyAll();
                System.out.println("take num==0");
                try {
                    lock.wait();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }

            E e = list.removeFirst();
            num--;
            lock.notifyAll();
            System.out.println("take num > 0");
            return e;
        }
    }

}

测试代码是这样子的:

LinkedListWrapper l = new ...;
new Thread(new Runnable() {

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            i++;
            l.put("---->" + i);
        }
    }
}).start();

new Thread(new Runnable() {

    @Override
    public void run() {
        while (true) {
            System.out.println("===>"+l.take());
        }
    }
}).start();

一个线程往队列中放,一个从队列中取。运行一下发现挺好,队列满了阻塞了,取时如果队列为空也会阻塞。到这种程度我就认为是很完美的实现了目标,并没有更深入的思考这种实现方式的问题,直到后来。。。测试代码变成了这样:

new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    i++;
                    l.put("--1-->" + i);
                    System.out.println("put1");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"1").start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    i++;
                    l.put("--2-->" + i);
                    System.out.println("put2");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"2").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println("==1=>" + l.take());
                }
            }
        },"3").start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println("==2=>" + l.take());
                }
            }
        },"4").start();

和上面的测试代码区别就是线程变多了,2个放的,2个取的。
这时候发现问题了:take方法的take num==0一直在输出。为什么呢?因为第三个线程take的时候发现队列为空,就会输出这句话,然后notifyAll,接着自己就wait了。而notifyAll唤醒了线程4,这时候队列可能依然为空,线程4又去notifyAll了线程3,接着线程4wait了。也就是说线程3和4相互唤醒着玩了。。。即使队列为空。。。至于我是为啥发现了以前自己犯的错误,大家就忽略吧。。。
这种情况下该怎么正确的实现阻塞队列呢?其实大家可以看看java的阻塞队列怎么实现的。简单来说就是借助Condition来做的。下面一起来看一个简单版的Condition实现的阻塞队列吧:

public class LinkedListWrapper2<E> {
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    private final int max = 10;
    private int num = 0;

    private LinkedList<E> list = new LinkedList<>();

    public void put(E e) {
        lock.lock();
        try {
            while (num == max) {
                try {
                    notFull.await();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            list.addLast(e);
            num++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E take() {
        lock.lock();
        try {
            while (num == 0) {
                try {
                    notEmpty.await();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }
            E e = list.removeFirst();
            num--;
            notFull.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }
}

上面这种实现方式是一种比较正确的实现方式,而且java的LinkedBlockingQueue就是这样实现的。大家可以参考java的实现方式具体学习一下Condition的使用。
第二种实现方式无论多少个线程测试,都是没有问题的。至于Condition的实现原理,还没有深入研究,这里就不做讨论了。。。
关于Condition和synchronize的使用,这也算是吃一堑长一智了。。。

猜你喜欢

转载自blog.csdn.net/lizhengwei1989/article/details/79437669