Java 9 API为并发应用提供了几种数据结构。从中强调如下两种数据结构:
- LinkedTransferQueue:此数据结构假定用于具有生产者/消费者结构的程序中。在这类应用中,有多个数据生产者和消费者,并且它们全部共享一个数据结构。生产者将数据添加到数据结构中,消费者从中取出数据。如果数据结构为空,消费者将被阻塞直到数据结构中有数据。如果填满数据,生产者将被阻塞直到数据结构有空间添加数据。
- PriorityBlockingQueue:在这种数据结构中,元素按照顺序进行存储。这些元素需要实现包含compareTo()方法的Comparable接口。当在结构中插入元素时,此元素与结构中的元素进行比较,直到找到对应位置。
LinkedTransferQueue的元素以它们到达时的顺序存储,所以提前到达的元素首先被消费。当开发一个生产者/消费者程序时,可能会出现这种情况,程序中数据是根据优先级而不是到达时间消耗的。本节讲学习如何实现应用于生产者/消费者问题中的数据结构,其元素将按照优先级排序,即高优先级的元素首先被消费。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为MyPriorityTransferQueue的类,继承PriorityBlockingQueue类且实现TransferQueue接口:
public class MyPriorityTransferQueue<E> extends PriorityBlockingQueue<E> implements TransferQueue<E> {
-
声明名为counter的私有AtomicInteger属性,存储等待消费元素的消费者数量:
private final AtomicInteger counter;
-
声明名为transferred的私有LinkedBlockingQueue属性:
private final LinkedBlockingQueue<E> transfered;
-
声明名为lock的私有ReentrantLock属性:
private final ReentrantLock lock;
-
实现类构造函数,初始化属性:
public MyPriorityTransferQueue() { counter=new AtomicInteger(0); lock=new ReentrantLock(); transfered=new LinkedBlockingQueue<E>(); }
-
实现tryTransfer()方法,如果可能的话,此方法试图立即发送元素给等待的消费者。如果没有任何消费者等待,此方法返回false:
@Override public boolean tryTransfer(E e) { boolean value=false; try { lock.lock(); if (counter.get() == 0) { value = false; } else { put(e); value = true; } } finally { lock.unlock(); } return value; }
-
实现transfer()方法,如果可能的话,此方法试图立即发送元素给等待的消费者。如果没有消费者等待,此方法将元素存储在一个特殊队列中,发送给第一个试图得到元素的消费者,并阻塞线程直到元素被消费:
@Override public void transfer(E e) throws InterruptedException { lock.lock(); if (counter.get()!=0) { try { put(e); } finally { lock.unlock(); } } else { try { transfered.add(e); } finally { lock.unlock(); } synchronized (e) { e.wait(); } } }
-
实现tryTransfer()方法,接收三个参数:元素、等待消费者的时间和指定等待的时间单位。如果有消费者在等待,则立即发送元素。否则,将等待时间转换成毫秒,并使用wait()方法设置线程休眠。当消费者得到元素时,如果线程还在wait()方法中休眠,需要使用modify()方法唤醒它,稍后将会看到:
@Override public boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException { lock.lock(); if (counter.get() != 0) { try { put(e); } finally { lock.unlock(); } return true; } else { long newTimeout=0; try { transfered.add(e); newTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit); } finally { lock.unlock(); } e.wait(newTimeout); lock.lock(); boolean value; try { if (transfered.contains(e)) { transfered.remove(e); value = false; } else { value = true; } } finally { lock.unlock(); } return value; } }
-
实现hasWaitingConsumer()方法,使用counter属性值计算此方法的返回值,如果counter值大于零,返回true,否则返回false:
@Override public boolean hasWaitingConsumer() { return (counter.get()!=0); }
-
实现getWaitingConsumerCount()方法,返回counter属性值:
@Override public int getWaitingConsumerCount() { return counter.get(); }
-
实现take()方法,当消费者想要消费一个元素时调用此方法。首先,获取之前定义的锁,递增等待消费者的数量:
@Override public E take() throws InterruptedException { lock.lock(); E value=transfered.poll(); try { counter.incrementAndGet();
-
如果传输队列中没有任何元素,释放锁并且使用take()方法试图从队列中得到元素,然后再次获取锁。如果队列中没有任何元素,此方法将设置线程休眠直到有元素待消费:
if (value==null) { lock.unlock(); value=super.take(); lock.lock();
-
否则如果有元素的话,将元素从传输队列中取走,唤醒等到消费元素的线程。考虑到正在同步的是来自类外面的对象,需要保证这个对象不允许在应用的其它地方用来锁定:
} else { synchronized (value) { value.notify(); } }
-
最后,递减等待消费者的数量,释放锁:
counter.decrementAndGet(); } finally { lock.unlock(); } return value; } }
-
接下来,实现名为Event的类,实现Event类参数化的Comparable接口:
public class Event implements Comparable<Event>{
-
声明名为thread的私有String属性,存储创建事件的线程名称:
private final String thread;
-
声明名为priority的私有int属性,存储事件优先级:
private final int priority;
-
实现类构造函数,初始化属性:
public Event(String thread, int priority){ this.thread=thread; this.priority=priority; }
-
实现返回thread属性值的方法:
public String getThread() { return thread; }
-
实现返回priority属性值的方法:
public int getPriority() { return priority; }
-
实现compareTo()方法,用来比较实际事件和作为参数传递的事件。如果实际事件比参数优先级高,则返回-1,反之返回1,如果两个事件优先级相同返回0。这样将会得到按照优先级降序排序的列表,高优先级的事件将会最先存储到队列中:
@Override public int compareTo(Event e) { return Integer.compare(e.priority, this.getPriority()); } }
-
实现名为Producer的类,实现Runnable接口:
public class Producer implements Runnable{
-
声明名为buffer的Event类参数化的私有MyPriorityTransferQueue属性,存储生产者创建的事件:
private final MyPriorityTransferQueue<Event> buffer;
-
实现类构造函数,初始化属性:
public Producer(MyPriorityTransferQueue<Event> buffer) { this.buffer=buffer; }
-
实现类的run()方法,使用按照优先级排序(最后的事件优先级最高)创建100个Event对象,使用put()方法将它们插入到队列中:
@Override public void run() { for (int i=0; i<100; i++) { Event event=new Event(Thread.currentThread().getName(),i); buffer.put(event); } } }
-
实现名为Consumer的类,实现Runnable接口:
public class Consumer implements Runnable {
-
声明名为buffer的Event类参数化的私有MyPriorityTransferQueue属性,得到类消费的事件:
private final MyPriorityTransferQueue<Event> buffer;
-
实现类构造函数,初始化属性:
public Consumer(MyPriorityTransferQueue<Event> buffer) { this.buffer=buffer; }
-
实现run()方法,使用take()方法消费1002个事件(本范例中生成的所有事件),并且输出生成事件的线程数量和它们的优先级到控制台:
@Override public void run() { for (int i=0; i<1002; i++) { try { Event value=buffer.take(); System.out.printf("Consumer: %s: %d\n",value.getThread(), value.getPriority()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
通过创建名为Main的类,添加main()方法,实现本范例主类:
public class Main { public static void main(String[] args) throws Exception {
-
创建名为buffer的MyPriorityTransferQueue对象:
MyPriorityTransferQueue<Event> buffer=new MyPriorityTransferQueue<Event>();
-
创建Producer任务,加载10个线程执行此任务:
Producer producer=new Producer(buffer); Thread producerThreads[]=new Thread[10]; for (int i=0; i<producerThreads.length; i++) { producerThreads[i]=new Thread(producer); producerThreads[i].start(); }
-
创建和加载Consumer任务:
Consumer consumer=new Consumer(buffer); Thread consumerThread=new Thread(consumer); consumerThread.start();
-
输出实际消费者数到控制台:
System.out.printf("Main: Buffer: Consumer count: %d\n", buffer.getWaitingConsumerCount());
-
使用transfer()方法将事件传送给消费者:
Event myEvent=new Event("Core Event",0); buffer.transfer(myEvent); System.out.printf("Main: My Event has ben transfered.\n");
-
使用join()方法等到生产者结束:
for (int i=0; i<producerThreads.length; i++) { try { producerThreads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } }
-
设置线程休眠1秒钟:
TimeUnit.SECONDS.sleep(1);
-
输出实际消费者数到控制台:
System.out.printf("Main: Buffer: Consumer count: %d\n", buffer.getWaitingConsumerCount());
-
使用transfer()方法传送另一个事件:
myEvent=new Event("Core Event 2",0); buffer.transfer(myEvent);
-
使用join()方法等到消费者结束:
consumerThread.join();
-
输出指明程序结束的信息到控制台:
System.out.printf("Main: End of the program\n"); } }
工作原理
本节实现了MyPriorityTransferQueue数据结构,这是在生产者/消费者问题中用到的数据结构。但其元素按照优先级排序,而不是按照它们到达的顺序。由于Java不允许多继承性,所以做出的第一个决定与MyPriorityTransferQueue类的基类有关。通过继承此类,能够使用PriorityBlockingQueue中实现的操作,而不用再实现这些操作。 范例还实现了TransferQueue方法,添加相关的方法给生产者/消费者。之所以做出这样的选择,是因为与PriorityBlockingQueue类中实现的方法相比,实现TransferQueue接口的方法更容易。然而,我们还可以实现继承自LinkedTransferQueue类的类,实现必要的方法来获取自定义的PriorityBlockingQueue类。
MyPriortyTransferQueue类包含如下三个属性:
- 名为counter的AtomicInteger属性:存储等待从数据结构取走元素的消费者数量。当消费者调用take()操作从数据结构中取走元素时,计数器增加。当消费者结束take()操作执行时,计数器再次减少。这个计数器在hasWaitingConsumer()和getWaitingConsumerCount()方法实现中使用。
- 名为lock的ReentrantLock属性:用于控制对实现操作的访问。根据这个属性,只有一个线程可以处理数据结构。
- 最后是存储传输元素的LinkedBlockingQueue列表。
在MyPriorityTransferQueue中实现了一些方法,TransferQueue接口中声明了所有方法,且在PriorityBlockingQueue接口中实现了take()方法。这些在之前讲解过,如下是其它方法的介绍:
- tryTransfer(E e) :此方法试图直接将元素发送给消费者。如果有消费者等待,它将立即被消费者消费的元素存储到优先级队列中,然后返回true值。如果没有消费者等待,返回false值。
- transfer(E e) :此方法直接将元素发送给消费者。如果有消费者等待,它将立即被消费者消费的元素存储到优先级队列中。否则元素存储到传输元素列表中,且线程被阻塞直到元素被消费。当线程进入休眠状态时,必须释放锁,否则做将阻塞队列。
- tryTransfer(E e, long timeout, TimeUnit unit) :此方法与transfer()方法类似,但此方法中线程阻塞的周期时间通过参数决定。当线程进入休眠状态时,必须释放锁,否则将阻塞队列。
- take():此方法返回待消费的下一个元素,如果传输元素列表中有元素,则此元素从列表中取出,否则从优先级队列中取出元素。
一旦实现了数据结构,就实现了Event类,这是元素已经存储到数据结构中的类。Event类有两个属性存储生产者ID和事件优先级,并且由于数据结构的需求,实现了Comparable接口。
然后,实现了Producer和Consumer类。本范例中,包含10个生产者和1个消费者共享相同的缓冲区。每个生产者创建具有递增优先级的100个事件,所以具有高优先级事件是最后生成的。
范例主类创建了MyPriorityTransferQueue对象、10个生产者和1个消费者,使用MyPriorityTransferQueue类的transfer()方法传输两个事件到缓冲区。
下图显示本范例在控制台输出的部分执行信息:
可以看出具有高优先级的时间首先被消费,以及消费者如何使用转移的事件。
更多关注
- 第七章“并发集合”中的“使用按优先级排序的阻塞线程安全队列”和“使用阻塞线程安全双端队列”小节