并发编程之阻塞队列

什么是BlockingQueue

线程通信工具,在任一时刻,不管并发有多高,在单机JVM上面,同一时间永远只能有1个线程进行入队
或出队操作。
在这里插入图片描述

基础操作API

在这里插入图片描述

ArrayBlockingQueue 简单例子

public class Producer {
    public  ArrayBlockingQueue producerQuene;
    public int size;
    public Producer(int size) {
        this.size=size;
        this.producerQuene = new ArrayBlockingQueue(size);
    }

    public void produce(int data){
        try {
            producerQuene.put(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Producer p=new Producer(10);
        for (int i = 0; i < 10; i++) {
            try {
                p.producerQuene.put(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产完毕");
        System.out.println("消费者准备启动");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Consumer consumer=new Consumer(p);
        consumer.consume();
    }
}
public class Consumer {
    public Producer producer;

    public Consumer(Producer producer) {
        this.producer = producer;
    }

    public void consume() {
        while (true) {
            Object obj = this.producer.producerQuene.poll();

            if (obj == null) {
                break;
            }
            System.out.println("开始消费:" + obj);

        }
    }
}

在这里插入图片描述

SynchronousQueue 简单例子

SynchronousQueue 也是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),
如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程,
同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(当然也可以先take后put,原理是一样的)。

public class SynchronousQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        final SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();

        Thread putThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("put thread start");
                try {
                    queue.put(1);
                } catch (InterruptedException e) {
                }
                System.out.println("put thread end");
            }
        });

        Thread takeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("take thread start");
                try {
                    System.out.println("take from putThread: " + queue.take());
                } catch (InterruptedException e) {
                }
                System.out.println("take thread end");
            }
        });

        putThread.start();
        Thread.sleep(1000);
        takeThread.start();
    }
}

在这里插入图片描述
注意上图的结果不是固定的

但是,put线程与take线程是一对一传递消息的模型。

PriorityBlockingQueue

public class Person implements Comparable<Person> {
    private int id;
    private String name;
    private int i=0;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Person(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public Person() {
    }
    @Override
    public String toString() {
        return this.id + ":" + this.name;
    }
    @Override
    public int compareTo(Person person) {
        this.i--;
        return i;
    }
    public static void main(String[] args) {
        PriorityBlockingQueue<Person> pbq = new PriorityBlockingQueue<>();
        pbq.add(new Person(3,"person3"));
        System.err.println("容器为:" + pbq);
        pbq.add(new Person(2,"person2"));
        System.err.println("容器为:" + pbq);
        pbq.add(new Person(1,"person1"));
        System.err.println("容器为:" + pbq);
        pbq.add(new Person(4,"person4"));
        System.err.println("容器为:" + pbq);
    }
 }

在这里插入图片描述

take和put源码简析

主要是看put和take,想深入研究的可以看看。原理就是Lock加Contidion

public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);//创建锁
        //俩个成员变量,lock对象的方法产生
        notEmpty = lock.newCondition();//条件对象
        notFull =  lock.newCondition();
}
/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
 private final Condition notFull;

这俩个都是条件对象

解释下:一旦队列放满了,放满了后锁继续由生产者持有,那么消费者就没法操作了。
同样一旦队列空了,锁继续由消费者持有,生产者也没法生产往里放了。

public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)//队列放满
                notFull.await();
            enqueue(e);//没满走这里
        } finally {
            lock.unlock();
        }
}

当队列放满,
在这里插入图片描述
我们进入await方法。
当生产者或消费者不持有锁的时候,加入条件等待队列

 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//加入条件等待队列
            int savedState = fullyRelease(node);//释放锁,唤醒结点
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//判断是在条件队列还是同步队列里
                LockSupport.park(this);//阻塞线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
 }

addConditionWaiter方法

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {//判断无效的队列
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //Node.CONDITION条件等待
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
}

在这里插入图片描述
入队后,锁还没有释放,那么锁在这个方法里释放

int savedState = fullyRelease(node);

线程获取锁的条件是:只有在CLH队列里等待的node节点,并且node节点的前驱节点是signal可被唤醒
在这里插入图片描述

当阻塞队列有数据可供消费者消费时,条件队列中等待的数据转移至CLH队列,进行后续获取锁的操作。条件队列里的线程是不可以获取锁的!

简单总结

在这里插入图片描述
条件队列里的线程要想获取锁,必须条件满足了。

生产者往阻塞队列里去放,消费者去阻塞队列里去取,条件是队列里不能为空,如果为空了消费者是没有办法取的,如果不为空的话,就可以被唤醒去取,哪怕阻塞队列里没有放满。

只要生产者往阻塞队列里放东西了,就会通知条件队列里的线程马上转移至CLH队列里,准备去获取锁。

同步阻塞队列是基于AQS的应用
AQS实现的底层是基于CLH队列和条件队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值