Java并发编程之LinkedBlockingQueue队列怎么使用
Java并发编程之LinkedBlockingQueue队列怎么使用
这篇文章主要介绍了Java并发编程之LinkedBlockingQueue队列怎么使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java并发编程之LinkedBlockingQueue队列怎么使用文章都会有所收获,下面我们一起来看看吧。
LinkedBlockingQueue也是使用单向链表实现的,其也有两个Node,分别用来存放首、尾节点,并且还有一个初始值为0的原子变量count,用来记录队列元素个数。另外还有两个ReentrantLock的实例,分别用来控制元素入队和出队的原子性,其中takeLock用来控制同时只有一个线程可以从队列头获取元素,其他线程必须等待,putLock控制同时只能有一个线程可以获取锁,在队列尾部添加元素,其他线程必须等待。另外,notEmpty 和 notFull 是条件变量,它们内部都有一个条件队列用来存放进队和出队时被阻塞的线程,其实这是生产者-消费者模型。如下是独占锁的创建代码。
privatefinalAtomicIntegercount=newAtomicInteger();/**Lockheldbytake,poll,etc*/privatefinalReentrantLocktakeLock=newReentrantLock();/**Waitqueueforwaitingtakes*/privatefinalConditionnotEmpty=takeLock.newCondition();/**Lockheldbyput,offer,etc*/privatefinalReentrantLockputLock=newReentrantLock();/**Waitqueueforwaitingputs*/privatefinalConditionnotFull=putLock.newCondition();
当调用线程在LinkedBlockingQueue 实例上执行take、poll 等操作时需要获取到 takeLock 锁,从而保证同时只有一个线程可以操作链表头节点。另外由于条件变量 notEmpty 内部的条件队列的维护使用的是takeLock的锁状态管理机制,所以在调用notEmpty的await 和signal方法前调用线程必须先获取到 takeLock锁,否则会抛出IllegalMonitorStateException 异常。notEmpty内部则维护着一个条件队列,当线程获取到takeLock 锁后调用 notEmpty的await 方法时,调用线程会被阻塞,然后该线程会被放到notEmpty内部的条件队列进行等待,直到有线程调用了notEmpty的 signal 方法。
在LinkedBlockingQueue实例上执行put、offer等操作时需要获取到putLock锁,从而保证同时只有一个线程可以操作链表尾节点。同样由于条件变量 notFull 内部的条件队列的维护使用的是putLock的锁状态管理机制,所以在调用 notFull 的 await 和 signal 方法前调用线程必须先获取到putLock锁,否则会抛出 IllegalMonitorStateException 异常。notFull 内部则维护着一个条件队列,当线程获取到 putLock 锁后调用notFull的await 方法时,调用线程会被阻塞,然后该线程会被放到notFull 内部的条件队列进行等待,直到有线程调用了 notFull 的 signal 方法。如下是LinkedBlockingQueue 的无参构造函数的代码。
如下是LinkedBlockingQueue的无参构造代码
publicstaticfinalintMAX_VALUE=0x7fffffff;publicLinkedBlockingQueue(){this(Integer.MAX_VALUE);}publicLinkedBlockingQueue(intcapacity){if(capacity<=0)thrownewIllegalAgrumentException();this.capacity=capacity;last=head=newNode<E>(null);}
由该代码可知,默认队列容量为0x7fffffff,用户也可以自己指定容量,所以从一定程度上可以说LinkedBlockingQueue是有界阻塞队列。
offer操作
publicbooleanoffer(Ee){//(1)if(e==null)thrownewNullPointerException();//(2)finalAtomicIntegercount=this.count;if(count.get()==capacity)returnfalse;//(3)intc=-1;Node<E>node=newNode<E>(e);finalReentrantLockputLock=this.putLock;putLock.lock();try{//(4)if(count.get()<capacity){enqueue(node);c=count.getAndIncrement();//(5)if(c+1<capacity)notFull.signal();}}finally{//(6)putLock.unlock();}//(7)if(c==0)signalNotEmpty();//(8)returnc>=0;}
代码(2)判断如果当前队列已满则丢弃当前元素并返回false
代码(3)获取到 putLock 锁,当前线程获取到该锁后,则其他调用put和 offer操的线程将会被阻塞(阻塞的线程被放到putLock锁的AQS阻塞队列)。
代码(4)这里重新判断当前队列是否满,这是因为在执行代码(2)和获取到 putLock 锁期间可能其他线程通过 put 或者offer 操作向队列里面添加了新元素。重新判斯队列确实不满则新元素入队,并递增计数器。
代码(5)判断如果新元素入队后队列还有空闲空间,则唤醒notFull的条件队列里面因为调用了notFull的await操作(比如执行put方法而队列满了的时候)而被阻塞的一个线程,因为队列现在有空闲所以这里可以提前唤醒一个入队线程。
代码(6)则释放获取的putLock 锁,这里要注意,锁的释放一定要在finally里面做因为即使try块抛出异常了,finally也是会被执行到。另外释放锁后其他因为调用put 操作而被阻塞的线程将会有一个获取到该锁。
代码(7)中的c0说明在执行代码(6)释放锁时队列里面至少有一个元素,队列里面有元素则执行signalNotEmpty操作.
关于“Java并发编程之LinkedBlockingQueue队列怎么使用”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“Java并发编程之LinkedBlockingQueue队列怎么使用”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注恰卡编程网行业资讯频道。