LinkedBlockingQueue的使用

    首先来看看LinkedBlockingQueue类的定义

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable

   java文档中对于 LinkedBlockingQueue类的描述如下:

   LinkedBlockingQueue是一个基于线性结点拥有一个可选的边界的阻塞队列。这个队列遵守先进先出原则(FIFO),在头(head)中的结点是存在队列时间最长的结点,在尾(tail)的结点则是存在队列时间最短的结点。新插入的结点都放置在队列的尾部,检索的结点都是在头部。基于线性结点的阻塞队列拥有比基于数组的阻塞队列更高的吞吐量,但是降低了性能。

   可选的容量边界防止了整个阻塞队列过度的膨胀。如果没有指定容量,那么这个阻塞队列的容量将是Integer.MAX_VALUE.

  这个类实现了Collection和Iterator两个接口所有的可选方法。

  使用LinkedBlockingQueue可以很好的解决生产者-消费者的模型,当生产过程和消费过程不均的时候,可以通过阻塞的方法使用双边达到平衡。另一方面,可以不加锁的方式操作同一个数据对象,而不变担心多线程导致的数据不一致的问题。

   下面使用一个例子来说明一下LinkedBlockingQueue的使用,采用30个线程向只有20容量的队列中添加数据,另外采用2个线程去消费

package com.app.bean;

public class Student {
	private String stu_name = "";

	public Student(String name) {
		this.stu_name = name;
	}

	@Override
	public String toString() {
		return "student_nu:" + stu_name;
	}
}
package com.app.blockingqueue;

import java.util.concurrent.LinkedBlockingQueue;

import com.app.bean.Student;

public class LinkedBlockingQueueTest {

	public static void main(String[] args) {

		final LinkedBlockingQueue<Student> queue = new LinkedBlockingQueue<Student>(
				10);
		// create 20 threads to put elements
		for (int i = 0; i < 30; i++) {
			final int index = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						queue.put(new Student("" + index));
						System.out.println("-----------put element:" + index);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
		// only create 2 threads to get elements
		for (int i = 0; i < 2; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true) {
						try {
							Student stu = queue.take();
							System.out.println("get element:" + stu.toString());
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}).start();
		}
	}
}

LinkedBlockingQueue的使用

返回的结果可以看到,put与take操作交互进行直到所有的数据操作完成。

下面来看看LinkedBlockingQueue的内部构造。既然是线性表,那么必就会有一个线性结点。

static class Node<E> {
        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<E> next;

        Node(E x) { item = x; }
    }

该内部类Node就是线性表的内部结点的定义。包含了一个值的定义,和一个指向下个结点的后继。

   private final int capacity;

    /** Current number of elements */
    private final AtomicInteger count = new AtomicInteger(0);

    /**
     * Head of linked list.
     * Invariant: head.item == null
     */
    private transient Node<E> head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     */
    private transient Node<E> 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();

 capacity: 代表了当前链表的最大容量

 count:代表了当前链表中实际的容量

 head:代表了此链表的头结点,该结点中的item = null

 last:代表了最后一个结点

 takLock:取数据锁

 notEmpty:取数据时的条件对象,当链表没有数据的时候,调用await()方法挂起线程

 putLock:放数据锁

 notFull:放数据时的条件对象,当链表数据满了的时候,调用await()方法挂起线程

 private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

 通知一个线程,当前的链表容量不为空,可以进行取操作。将该线程从等待集中移除。

 private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

通知一个线程,当前的链表没有满,可以进行插入操作。将该线程从等待集中移除。

private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }

插入操作的核心操作,将当前的last结点修改为node。

private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

将当前head结点的next结点设置为head结点,并且设置item=null,返回该item。

/**
     * 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();
    }

   fullLock()和fullUnLock()方法,分别对插入和提取操作加锁和解锁。

 public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
 
   public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }
    
     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 new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

构造函数,带一个容量参数的构造函数,会设置一个head结点和last结点,并且这两个结点指向同一个对象,传递的参数则设置给capacity设置为容量。带集合类型的参数,则是设置容量为Integer.MAX_VALUE,并且将集合中的数据全部添加到线性表中,并且设置当前的大小,也就是count的大小。

public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        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>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }

offer的该方法实现了计时等待,等待的时间是通过while循环去控制的,如果当前的容量满的时候,while循环回去不断的统计timeout - nanos消耗的值,当这个值小于等于0的时候,并且这个时候,阻塞队列依旧满的时候,返回一个false值;如果阻塞队列不满的时候,调用enquue()方法,添加数据。

其他的方法几个方法,与该方法类似,不再过多赘述。

你可能感兴趣的:(java)