List作为最基本的集合,包含没有限制数量的元素,可以在任何位置增加、读取,或者删除元素。并发列表允许不同线程在同一时间添加或删除来自列表中的元素,而不会产生任何数据非一致性错误。与列表相似的,还有双端队列,其数据结构与队列相近,但在双端队列中,可以在前(头)后(尾)端添加或删除。
本节将学习在并发应用中如何使用阻塞双端队列。阻塞和非阻塞双端队列的主要区别是,阻塞双端队列的插入和删除元素方法,如果因为列表已满或者为空而没有立即执行,则阻塞发起调用的线程,直到可以执行操作。Java提供了LinkedBlockingDeque类来实现阻塞双端队列。
将要实现的范例中包含如下两个不同任务:
- 向双端队列添加大量元素
- 从相同的列表中大规模删除数据
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为Client的类,指定其实现Runnable接口:
public class Client implements Runnable {
-
声明名为requestList的String类参数化的私有LinkedBlockingDeque属性:
private final LinkedBlockingDeque<String> requestList;
-
实现类构造函数,初始化属性:
public Client (LinkedBlockingDeque<String> requestList) { this.requestList=requestList; }
-
实现run()方法,使用requestList对象的put()方法每隔一秒向双端队列插入五个String对象,循环重复三次:
@Override public void run() { for (int i=0; i<3; i++) { for (int j=0; j<5; j++) { StringBuilder request=new StringBuilder(); request.append(i); request.append(":"); request.append(j); try { requestList.put(request.toString()); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Client added: %s at %s.\n",request, new Date()); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Client: End.\n"); } }
-
通过创建名为Main的类,添加main()方法,实现本范例主类:
public class Main { public static void main(String[] args) throws Exception{
-
声明和创建名为list的String类参数化的LinkedBlockingDeque属性,固定队列数量为3:
LinkedBlockingDeque<String> list=new LinkedBlockingDeque<>(3);
-
创建和启动一个Thread对象,执行Client任务:
Client client=new Client(list); Thread thread=new Thread(client); thread.start();
-
使用列表对象的take()方法每隔300毫秒从列表中得到三个String对象,循环重复五次。输出字符串到控制台:
for (int i=0; i<5 ; i++) { for (int j=0; j<3; j++) { String request=list.take(); System.out.printf("Main: Removed: %s at %s. Size: %d\n", request,new Date(), list.size()); } TimeUnit.MILLISECONDS.sleep(300); }
-
输出信息指明程序执行结束:
System.out.printf("Main: End of the program.\n"); } }
工作原理
在本节中,使用String类参数化的LinkedBlockingDeque对象来处理数据的非阻塞并发双端队列。
Client类使用put()方法将字符串插入到双端队列,如果双端队列已满(因为固定了队列大小),此方法将阻塞线程执行,直到列表中有位置为空。
Main类使用take()方法从双端队列中获取字符串。如果队列为空,此方法阻塞线程执行,直到双端队列中存在元素。
如果本范例中用到的LinkedBlockingDeque类的方法在线程阻塞的时候被中断,这些方法都能够抛出InterruptedException异常。所以,添加必要的代码来捕捉这些异常。
扩展学习
LinkedBlockingDeque类也提供插入和从双端队列中获取元素的方法,这些方法不是被阻塞,而是抛出异常或者返回null值。如下所示:
- takeFirst()和takeLast():这两个方法分别返回双端队列的第一个和最后一个元素,删除从双端队列中返回的元素。如果双端队列为空,则阻塞线程直到双端队列中存在元素。
- getFirst()和getLast():这两个方法分别返回双端队列的第一个和最后一个元素,不删除从双端队列中返回的元素。如果双端队列为空,则抛出NoSuchElementExcpetion异常。
- peek()、peekFirst()和peekLast() :这些方法分别返回双端队列的第一个和最后一个元素,不删除从双端队列中返回的元素。如果双端队列为空,返回null值。
- poll()、pollFirst()和pollLast():pollFirst()和pollLast()方法分别返回双端队列的第一个和最后一个元素,删除从双端队列中返回的元素。如果双端队列为空,返回null值。
- add()、addFirst()和addLast():addFirst()和addLast()分别在双端队列的第一个和最后一个位置添加一个元素。如果队列已满(创建时固定大小),抛出IllegalStateException异常。
更多关注
- 本章“使用非阻塞线程安全双端队列”小节