All Downloads are FREE. Search and download functionalities are using the official Maven repository.

stream.io.BlockingQueue Maven / Gradle / Ivy

The newest version!
/*
 *  streams library
 *
 *  Copyright (C) 2011-2014 by Christian Bockermann, Hendrik Blom
 * 
 *  streams is a library, API and runtime environment for processing high
 *  volume data streams. It is composed of three submodules "stream-api",
 *  "stream-core" and "stream-runtime".
 *
 *  The streams library (and its submodules) is free software: you can 
 *  redistribute it and/or modify it under the terms of the 
 *  GNU Affero General Public License as published by the Free Software 
 *  Foundation, either version 3 of the License, or (at your option) any 
 *  later version.
 *
 *  The stream.ai library (and its submodules) is distributed in the hope
 *  that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package stream.io;

import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import stream.Data;

/**
 * 

* The default implementation of a blocking queue of data items. *

* * @author Hendrik Blom, Christian Bockermann * */ public class BlockingQueue extends AbstractQueue { /** * Linked list node class */ 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) */ Node next; Node(E x) { item = x; } } private static final Logger log = LoggerFactory.getLogger(BlockingQueue.class); protected AtomicBoolean closed = new AtomicBoolean(false); /** Current number of elements */ private final AtomicInteger count = new AtomicInteger(0); /** * Head of linked list. Invariant: head.item == null */ private transient Node head; /** * Tail of linked list. Invariant: last.next == null */ private transient Node last; /** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition(); /** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public BlockingQueue() { last = head = new Node(null); } /** * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. * * @param capacity * the capacity of this queue * @throws IllegalArgumentException * if {@code capacity} is not greater than zero */ public BlockingQueue(int capacity) { super(); if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; } private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } } /** * Signals a waiting put. Called only from take/poll. */ private void signalNotFull() { final ReentrantLock putLock = this.putLock; putLock.lock(); try { notFull.signal(); } finally { putLock.unlock(); } } /** * Links node at end of queue. * * @param node * the node */ private void enqueue(Node node) { // assert putLock.isHeldByCurrentThread(); // assert last.next == null; last = last.next = node; } /** * Removes a node from head of queue. * * @return the node */ private Data dequeue() { // assert takeLock.isHeldByCurrentThread(); // assert head.item == null; Node h = head; Node first = h.next; h.next = h; // help GC head = first; Data x = first.item; first.item = null; return x; } /** * Lock to prevent both puts and takes. */ void fullyLock() { putLock.lock(); takeLock.lock(); } /** * Unlock to allow both puts and takes. */ void fullyUnlock() { takeLock.unlock(); putLock.unlock(); } public int size() { return count.get(); } public int remainingCapacity() { return capacity - count.get(); } /** * @see stream.io.QueueService#enqueue(stream.Data) */ public synchronized boolean enqueue(Data item) { log.trace("Queue {}: Enqueuing event {}", getId(), item); try { if (item == null) return false; if (closed.get()) return false; int c = -1; Node node = new Node(item); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() == capacity) { notFull.await(); } if (closed.get()) return false; enqueue(node); c = count.getAndIncrement(); // TODO log.debug("put size: {}", c); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; } catch (Exception e) { log.error("Error enqueuing item: {}", e.getMessage()); if (log.isDebugEnabled()) e.printStackTrace(); return false; } } /** * @see stream.io.Source#init() */ @Override public void init() throws Exception { if (getCapacity() < 1) { throw new IllegalArgumentException("Invalid queue-capacity '" + getCapacity() + "'!"); } } /** * @see stream.io.Stream#close() */ public void close() throws Exception { log.debug("Closing queue '{}'...", getId()); fullyLock(); try { if (closed.get()) { log.debug("Queue '{}' already closed.", getId()); return; } // log.debug("queue: {}", queue); closed.getAndSet(true); } finally { fullyUnlock(); } } /** * @see stream.io.AbstractStream#readItem(stream.Data) */ @Override public synchronized Data read() throws Exception { log.trace("Reading from queue {}", getId()); Data item = null; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { if (closed.get() && count.get() == 0) { log.debug("Queue '{}' is closed and empty => null", getId()); return null; } while (count.get() == 0) { notEmpty.await(); } item = dequeue(); c = count.getAndDecrement(); // TODO log.debug("take size: {}", c); log.trace("took item from queue: {}", item); if (c > 1) notEmpty.signal(); } catch (InterruptedException e) { if (closed.get() && count.get() == 0) { log.debug("Queue '{}' is closed and empty => null", getId()); return null; } else { log.error("Interruped while waiting for data: {}", e.getMessage()); if (log.isDebugEnabled()) e.printStackTrace(); } } // if (closed) { // log.debug("Reading from closed queue '{}'!", getId()); // return null; // } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return item; } /** * @see stream.io.QueueService#poll() */ public Data poll() { throw new IllegalAccessError("Not Implemented"); } public Data take() { try { return read(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * @see stream.io.Sink#write(stream.Data) */ @Override public boolean write(Data item) throws Exception { return enqueue(item); } @Override public boolean write(Collection data) throws Exception { log.trace("Queue {}: Enqueuing event {}", getId(), data); try { if (data == null) return false; if (closed.get()) return false; int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { // TODO MaxInt while (count.get() == capacity) { notFull.await(); } if (closed.get()) return false; for (Data item : data) { Node node = new Node(item); enqueue(node); c = count.getAndIncrement(); } log.debug("{}:{}", id, c); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; } catch (Exception e) { log.error("Error enqueuing item: {}", e.getMessage()); if (log.isDebugEnabled()) e.printStackTrace(); return false; } } /** * @see stream.io.Barrel#clear() */ @Override public int clear() { fullyLock(); int removed = count.get(); try { for (Node p, h = head; (p = h.next) != null; h = p) { h.next = h; p.item = null; } head = last; // assert head.item == null && head.next == null; if (count.getAndSet(0) == capacity) notFull.signal(); } finally { fullyUnlock(); } return removed; } /** * @see stream.io.QueueService#level() */ @Override public int level() { return count.get(); } /** * @see stream.io.QueueService#capacity() */ @Override public int capacity() { return capacity; } @Override public Integer getSize() { return count.get(); } /** * @see stream.service.Service#reset() */ @Override public void reset() throws Exception { } public String toString() { return "stream.io.BlockingQueue['" + id + "']"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy