Java阻塞队列poll_java阻塞队列之LinkedBlockingQueue

LinkedBlockingQueue是BlockingQueue中的其中一个,其实现方式为单向链表,下面看其具体实现。(均为JDK8)

一、构造函数

在LinkedBlockingQueue中有三个构造函数,如下图,

1aa43f22cb3b61463bb7ac6f12febcec.png

1、LinkedBlockingQueue()

这是一个无参的构造函数,其定义如下,

publicLinkedBlockingQueue() {this(Integer.MAX_VALUE);

}

在这个构造函数中调用了有参的构造函数,传入的整型值为Integer所能表示的最大值(0x7fffffff)。下面看这个带参数的构造方法。

2、LinkedBlockingQueue(int capacity)

这是一个整型参数的构造方法其,定义如下,

public LinkedBlockingQueue(intcapacity) {if (capacity <= 0) throw newIllegalArgumentException();this.capacity =capacity;

last= head = new Node(null);

}

从其实现上来看,其整型参数代表此队列的容量,即元素的最大个数;然后初始化了last和head两个变量,我们猜last应该是此队列的尾元素、head为此队列的头元素。这里有一个Node类,下面看Node类的实现,

static class Node{

E item;/*** One of:

* - the real successor Node

* - this Node, meaning the successor is head.next

* - null, meaning there is no successor (this is the last node)*/Nodenext;

Node(E x) { item=x; }

}

Node是LinkedBlockingQueue的静态内部类,也是组成此队列的节点元素,其内部有两个属性,一个Item代表节点元素,next指向下一个Node节点,这样就可以得出LinkedBlockingQueue的结构是使用Node连接起来,头节点为head,尾节点为last。

2beafad19390cf15b9540c22558400cd.png

3、LinkedBlockingQueue(Collection extends E> c)

此构造方法是使用一个集合类进行构造,其定义如下

public LinkedBlockingQueue(Collection extends E>c) {this(Integer.MAX_VALUE);final ReentrantLock putLock = this.putLock;

putLock.lock();//Never contended, but necessary for visibility

try{int n = 0;for(E e : c) {if (e == null)throw newNullPointerException();if (n ==capacity)throw new IllegalStateException("Queue full");

enqueue(new Node(e));++n;

}

count.set(n);

}finally{

putLock.unlock();

}

}

4、LinkedBlockingQueue的属性

从上面的构造函数中可以大体了解其属性有容量(capacity),队列中的元素数量(count)、头节点(head)、尾节点(last)等,如下

/**The capacity bound, or Integer.MAX_VALUE if none*/

private final intcapacity;/**Current number of elements*/

private final AtomicInteger count = newAtomicInteger();/*** Head of linked list.

* Invariant: head.item == null*/

transient Nodehead;/*** Tail of linked list.

* Invariant: last.next == null*/

private transient Nodelast;/**Lock held by take, poll, etc*/

private final ReentrantLock takeLock = newReentrantLock();/**Wait queue for waiting takes*/

private final Condition notEmpty =takeLock.newCondition();/**Lock held by put, offer, etc*/

private final ReentrantLock putLock = newReentrantLock();/**Wait queue for waiting puts*/

private final Condition notFull = putLock.newCondition();

使用原子类AtomicInteger的count表示队列中元素的个数,可以很好的处理并发,AtomicInteger底层大都是使用乐观锁进行操作,多线程下是安全的。

关注下takeLock、putLock这两个属性,这里定义了两把锁,一把take锁,另一把put锁。通过两把可重入锁实现并发,与ArrayBlockingQueue的一把锁相比。

二、队列的操作

需要使用阻塞队列,那么就需要向队列中添加或取出元素,在LinkedBlockingQueue中已经实现了相关操作,对于添加/取出均是成对出现,提供的方法中有抛出异常、返回false、线程阻塞等几种情形。

1、put/take

put/take是一对互斥操作,put向队列中添加元素,take从队列中取出元素。

1.1、put

put方法定义如下,

public void put(E e) throwsInterruptedException {if (e == null) throw newNullPointerException();//Note: convention in all put/take/etc is to preset local var//holding count negative to indicate failure unless set.

int c = -1;

Node node = new Node(e);//获得添加锁,

final ReentrantLock putLock = this.putLock;//获得当前队列中的元素数量

final AtomicInteger count = this.count;

putLock.lockInterruptibly();try{/** Note that count is used in wait guard even though it is

* not protected by lock. This works because count can

* only decrease at this point (all other puts are shut

* out by lock), and we (or some other waiting put) are

* signalled if it ever changes from capacity. Similarly

* for all other uses of count in other wait guards.*/

//如果当前队列中元素的数量等于队列的容量,则阻塞当前线程,

while (count.get() ==capacity) {

notFull.await();

}

enqueue(node);//当前线程中元素数量增1,返回操作前的数量

c =count.getAndIncrement();//c+1其实是当前队列中元素的数量,如果比容量小,则唤醒notFull的操作,即可以进行继续添加,执行put等添加操作。

if (c + 1

notFull.signal();

}finally{

putLock.unlock();

}

//说明在执行enqueue前的数量为0,执行完enqueue后数量为1,则需要唤醒取进程。if (c == 0)

signalNotEmpty();

}

此方法的执行步骤大体如下,

判断要put的元素e是否为null,如果为null直接抛出空指针异常;

e不为null,则使用e创建一个Node节点,获得put锁;

判断当前队列中的元素数量和队列的容量,如果相等,则阻塞当前线程;

如果不相等,把生成的node节点插入队列,enqueue方法定义如下,

private void enqueue(Nodenode) {//assert putLock.isHeldByCurrentThread();//assert last.next == null;

last = last.next =node;

}

使用原子操作类把当前队列中的元素数量增1;如果添加后的队列中的元素数量比容量小,则表示可以继续执行put类的操作,唤醒notFull.singal();

如果c=0,即在enqueue前为空,数量为0(此时会阻塞take进程),enqueue后为1,则需要唤醒take进程,如下

private voidsignalNotEmpty() {final ReentrantLock takeLock = this.takeLock;

takeLock.lock();try{

notEmpty.signal();

}finally{

takeLock.unlock();

}

}

1.2、take

take方法和put刚好相反,其定义如下,

public E take() throwsInterruptedException {

E x;int c = -1;//获得当前队列的元素数量

final AtomicInteger count = this.count;//获得take锁

final ReentrantLock takeLock = this.takeLock;//执行take操作

takeLock.lockInterruptibly();try{while (count.get() == 0) {

notEmpty.await();//阻塞当前线程

}

x=dequeue();//当前队列的数量减1,返回操作前的数量

c =count.getAndDecrement();if (c > 1)

notEmpty.signal();

}finally{

takeLock.unlock();

}

//当前队列中元素数量为capacity-1,即未满,可以调用put方法,需要唤醒阻塞在put锁上的线程if (c ==capacity)

signalNotFull();returnx;

}

此方法的执行步骤大体如下,

获得take锁,表示执行take操作;

获得当前队列的元素数量,如果数量为0,则阻塞当前线程,直到被中断或者被唤醒;

如果当前队列的元素数量不额外i0,则执行出队操作;

privateE dequeue() {//assert takeLock.isHeldByCurrentThread();//assert head.item == null;

Node h =head;//head赋值给h

Node first =h.next;//相当于第二个节点赋值给first

h.next= h; //help GC

head =first;//头节点指向第二个节点

E x=first.item;

first.item= null;returnx;

}

从上面的代码可以看出把头节点进行出队,即head指向下一个节点

当前队列的元素数量减一,并返回操作前的数量;

如果之前大于1(c最小为2),指向dequeue后数量最小为1,证明队列中仍有元素,需要唤醒获得take锁的其他阻塞线程,take.singal();

如果c等于当前队列的容量(执行完dequeue后,当前队列中元素的数量等于capacity-1,则未满),则需要唤醒获得put锁的其他put线程;

private voidsignalNotFull() {final ReentrantLock putLock = this.putLock;

putLock.lock();try{

//唤醒阻塞在put锁的其他线程

notFull.signal();

}finally{

putLock.unlock();

}

}

2、offer/poll

2.1、offer方法

offer方法的定义如下,

public booleanoffer(E e) {if (e == null) throw newNullPointerException();final AtomicInteger count = this.count;if (count.get() ==capacity)return false;int c = -1;

Node node = new Node(e);final ReentrantLock putLock = this.putLock;

putLock.lock();try{if (count.get()

enqueue(node);

c=count.getAndIncrement();if (c + 1

notFull.signal();

}

}finally{

putLock.unlock();

}if (c == 0)

signalNotEmpty();return c >= 0;

}

此方法的执行步骤大体如下,

判断当前队列中的元素数量和队列容量,如果相等,直接返回false;

如果当前队列中元素数量小于队列容量,执行入队操作;

入队操作之后,判断队列中元素数量如果仍小于队列容量,唤醒其他的阻塞线程;

如果c==0(即入队成功,队列中元素的数量为1),则需要唤醒阻塞在put锁的线程;

2.2、poll

poll方法定义如下,

publicE poll() {final AtomicInteger count = this.count;if (count.get() == 0)return null;

E x= null;int c = -1;final ReentrantLock takeLock = this.takeLock;

takeLock.lock();try{if (count.get() > 0) {

x=dequeue();

c=count.getAndDecrement();if (c > 1)

notEmpty.signal();

}

}finally{

takeLock.unlock();

}if (c ==capacity)

signalNotFull();returnx;

}

此方法的执行步骤大体如下,

如果当前队列元素数量为0,直接返回null;

如果当前队列元素数量大于0,执行出队操作;

如果c>1,即c最小为2,则出队成功后,仍有1个元素,可以唤醒阻塞在take锁的线程;

如果c=capacity,则出队成功后,队列中的元素为capacity-1,这时队列为满,可以唤醒阻塞在put锁上的其他线程,即可以添加元素;

3、offer(E e, long timeout, TimeUnit unit)/poll(long timeout, TimeUnit unit)

3.1、offer(E e, long timeout, TimeUnit unit)

在规定的时间内阻塞线程,其定义如下

public boolean offer(E e, longtimeout, TimeUnit unit)throwsInterruptedException {if (e == null) throw newNullPointerException();long nanos =unit.toNanos(timeout);int c = -1;final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;

putLock.lockInterruptibly();try{while (count.get() ==capacity) {if (nanos <= 0)return false;

nanos=notFull.awaitNanos(nanos);

}

enqueue(new Node(e));

c=count.getAndIncrement();if (c + 1

notFull.signal();

}finally{

putLock.unlock();

}if (c == 0)

signalNotEmpty();return true;

}

此方法的执行步骤大体如下,

如果当前队列中元素的数量等于队列的容量,则在规定时间内会一直阻塞,如果超过了规定时间则直接返回false;

如果在规定时间内当前队列中元素的数量不等于队列容量,则跳出了while循环,则执行入队操作;

判断入队前队列中元素的数量,如果小于队列容量,则唤醒其他put锁的阻塞线程;如果等于0,则入队后元素数量大于0,则唤醒take锁阻塞的线程;

3.2、poll(long timeout, TimeUnit unit)

其方法定义如下,

public E poll(long timeout, TimeUnit unit) throwsInterruptedException {

E x= null;int c = -1;long nanos =unit.toNanos(timeout);final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;

takeLock.lockInterruptibly();try{while (count.get() == 0) {if (nanos <= 0)return null;

nanos=notEmpty.awaitNanos(nanos);

}

x=dequeue();

c=count.getAndDecrement();if (c > 1)

notEmpty.signal();

}finally{

takeLock.unlock();

}if (c ==capacity)

signalNotFull();returnx;

}

此方法的执行步骤大体如下,

如果当前队列中的元素数量为0,则在规定的时间内阻塞线程;如果超过了规定时间直接返回null;

执行出队操作,出队成功后,判断出队前队列中元素的数量,如果大于1(最小为2),则唤醒其他阻塞在take锁上的线程;

如果出队前队列中元素数量等于队列容量,则出队后队列中元素数量为capacity-1,则唤醒阻塞在put锁上的线程;

三、方法比较

3.1、添加方法比较

序号

方法名

队列满时处理方式

方法返回值

1

offer(E e)

返回false

boolean

2

put(E e)

线程阻塞,直到中断或被唤醒

void

3

offer(E e, long timeout, TimeUnit unit)

在规定时间内重试,超过规定时间返回false

boolean

3.2、取出方法比较

序号

方法名

队列空时处理方式

方法返回值

1

poll()

返回null

E

2

take()

线程阻塞,直到中断或被唤醒

E

3

poll(long timeout, TimeUnit unit)

在规定时间内重试,超过规定时间返回null

E

四、总结

以上是关于LinkedBlockingQueue队列的相关实现及方法介绍,此队列使用单向链表为载体,配合put/take锁实现生产线程和消费线程共享数据。LinkedBlockingQueue作为共享的数据池,实现并发环境下的添加及取出方法。

有不正之处欢迎指正,感谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值