AQS之阻塞队列
什么是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队列和条件队列