实现基于优先级的转移队列

Java 9并发编程指南 目录

实现基于优先级的转移队列

Java 9 API为并发应用提供了几种数据结构。从中强调如下两种数据结构:

  • LinkedTransferQueue:此数据结构假定用于具有生产者/消费者结构的程序中。在这类应用中,有多个数据生产者和消费者,并且它们全部共享一个数据结构。生产者将数据添加到数据结构中,消费者从中取出数据。如果数据结构为空,消费者将被阻塞直到数据结构中有数据。如果填满数据,生产者将被阻塞直到数据结构有空间添加数据。
  • PriorityBlockingQueue:在这种数据结构中,元素按照顺序进行存储。这些元素需要实现包含compareTo()方法的Comparable接口。当在结构中插入元素时,此元素与结构中的元素进行比较,直到找到对应位置。

LinkedTransferQueue的元素以它们到达时的顺序存储,所以提前到达的元素首先被消费。当开发一个生产者/消费者程序时,可能会出现这种情况,程序中数据是根据优先级而不是到达时间消耗的。本节讲学习如何实现应用于生产者/消费者问题中的数据结构,其元素将按照优先级排序,即高优先级的元素首先被消费。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为MyPriorityTransferQueue的类,继承PriorityBlockingQueue类且实现TransferQueue接口:

    public class MyPriorityTransferQueue<E> extends PriorityBlockingQueue<E> implements TransferQueue<E>  {
    
  2. 声明名为counter的私有AtomicInteger属性,存储等待消费元素的消费者数量:

    	private final AtomicInteger counter;
    
  3. 声明名为transferred的私有LinkedBlockingQueue属性:

    	private final LinkedBlockingQueue<E> transfered;
    
  4. 声明名为lock的私有ReentrantLock属性:

    	private final ReentrantLock lock;
    
  5. 实现类构造函数,初始化属性:

    	public MyPriorityTransferQueue() {
    		counter=new AtomicInteger(0);
    		lock=new ReentrantLock();
    		transfered=new LinkedBlockingQueue<E>();
    	}
    
  6. 实现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;
    	}
    
  7. 实现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();
    			}
    		}
    	}
    
  8. 实现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;
    		}
    	}
    
  9. 实现hasWaitingConsumer()方法,使用counter属性值计算此方法的返回值,如果counter值大于零,返回true,否则返回false:

    	@Override
    	public boolean hasWaitingConsumer() {
    		return (counter.get()!=0);
    	}
    
  10. 实现getWaitingConsumerCount()方法,返回counter属性值:

    	@Override
    	public int getWaitingConsumerCount() {
    		return counter.get();
    	}
    
  11. 实现take()方法,当消费者想要消费一个元素时调用此方法。首先,获取之前定义的锁,递增等待消费者的数量:

    	@Override
    	public E take() throws InterruptedException {
    		lock.lock();
    		E value=transfered.poll();
    		try {
    			counter.incrementAndGet();		
    
  12. 如果传输队列中没有任何元素,释放锁并且使用take()方法试图从队列中得到元素,然后再次获取锁。如果队列中没有任何元素,此方法将设置线程休眠直到有元素待消费:

    			if (value==null) {
    				lock.unlock();
    				value=super.take();
    				lock.lock();
    
  13. 否则如果有元素的话,将元素从传输队列中取走,唤醒等到消费元素的线程。考虑到正在同步的是来自类外面的对象,需要保证这个对象不允许在应用的其它地方用来锁定:

    			} else {
    				synchronized (value) {
    				value.notify();
    				}
    			}
    
  14. 最后,递减等待消费者的数量,释放锁:

    				counter.decrementAndGet();
    		} finally {
    			lock.unlock();
    		}
    		return value;
    	}
    }
    
  15. 接下来,实现名为Event的类,实现Event类参数化的Comparable接口:

    public class Event implements Comparable<Event>{
    
  16. 声明名为thread的私有String属性,存储创建事件的线程名称:

    	private final String thread;
    
  17. 声明名为priority的私有int属性,存储事件优先级:

    	private final int priority;
    
  18. 实现类构造函数,初始化属性:

    	public Event(String thread, int priority){
    		this.thread=thread;
    		this.priority=priority;
    	}
    
  19. 实现返回thread属性值的方法:

    	public String getThread() {
    		return thread;
    	}
    
  20. 实现返回priority属性值的方法:

    	public int getPriority() {
    		return priority;
    	}
    
  21. 实现compareTo()方法,用来比较实际事件和作为参数传递的事件。如果实际事件比参数优先级高,则返回-1,反之返回1,如果两个事件优先级相同返回0。这样将会得到按照优先级降序排序的列表,高优先级的事件将会最先存储到队列中:

    	@Override
    	public int compareTo(Event e) {
    		return Integer.compare(e.priority, this.getPriority());
    	}
    }
    
  22. 实现名为Producer的类,实现Runnable接口:

    public class Producer  implements Runnable{
    
  23. 声明名为buffer的Event类参数化的私有MyPriorityTransferQueue属性,存储生产者创建的事件:

    	private final MyPriorityTransferQueue<Event> buffer;
    
  24. 实现类构造函数,初始化属性:

    	public Producer(MyPriorityTransferQueue<Event> buffer) {
    		this.buffer=buffer;
    	}
    
  25. 实现类的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);
    		}
    	}
    }
    
  26. 实现名为Consumer的类,实现Runnable接口:

    public class Consumer implements Runnable {
    
  27. 声明名为buffer的Event类参数化的私有MyPriorityTransferQueue属性,得到类消费的事件:

    	private final MyPriorityTransferQueue<Event> buffer;
    
  28. 实现类构造函数,初始化属性:

    	public Consumer(MyPriorityTransferQueue<Event> buffer) {
    		this.buffer=buffer;
    	}
    
  29. 实现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();
    			}
    		}
    	}
    }
    
  30. 通过创建名为Main的类,添加main()方法,实现本范例主类:

    public class Main {
    	public static void main(String[] args) throws Exception {
    
  31. 创建名为buffer的MyPriorityTransferQueue对象:

    		MyPriorityTransferQueue<Event> buffer=new MyPriorityTransferQueue<Event>();
    
  32. 创建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();
    		}
    
  33. 创建和加载Consumer任务:

    		Consumer consumer=new Consumer(buffer);
    		Thread consumerThread=new Thread(consumer);
    		consumerThread.start();
    
  34. 输出实际消费者数到控制台:

    		System.out.printf("Main: Buffer: Consumer count: %d\n", buffer.getWaitingConsumerCount());
    
  35. 使用transfer()方法将事件传送给消费者:

    		Event myEvent=new Event("Core Event",0);
    		buffer.transfer(myEvent);
    		System.out.printf("Main: My Event has ben transfered.\n");
    
  36. 使用join()方法等到生产者结束:

    		for (int i=0; i<producerThreads.length; i++) {
    			try {
    				producerThreads[i].join();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
  37. 设置线程休眠1秒钟:

    		TimeUnit.SECONDS.sleep(1);
    
  38. 输出实际消费者数到控制台:

    		System.out.printf("Main: Buffer: Consumer count: %d\n", buffer.getWaitingConsumerCount());
    
  39. 使用transfer()方法传送另一个事件:

    		myEvent=new Event("Core Event 2",0);
    		buffer.transfer(myEvent);
    
  40. 使用join()方法等到消费者结束:

    		consumerThread.join();
    
  41. 输出指明程序结束的信息到控制台:

    		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()方法传输两个事件到缓冲区。

下图显示本范例在控制台输出的部分执行信息:

pics/08_06.jpg

可以看出具有高优先级的时间首先被消费,以及消费者如何使用转移的事件。

更多关注

  • 第七章“并发集合”中的“使用按优先级排序的阻塞线程安全队列”和“使用阻塞线程安全双端队列”小节

猜你喜欢

转载自blog.csdn.net/nicolastsuei/article/details/84632026