并发集合(六)使用线程安全的NavigableMap

使用线程安全的NavigableMap

Java API 提供的有趣的数据结构,并且你可以在并发应用程序中使用,它就是ConcurrentNavigableMap接口的定义。实现ConcurrentNavigableMap接口的类存储以下两部分元素:

  • 唯一标识元素的key
  • 定义元素的剩余数据

每部分在不同的类中实现。

Java API 也提供了这个接口的实现类,这个类是ConcurrentSkipListMap,它实现了非阻塞列表且拥有ConcurrentNavigableMap的行为。在内部实现中,它使用Skip List来存储数据。Skip List是基于并行列表的数据结构,它允许我们获取类似二叉树的效率。使用它,你可以得到一个排序的数据结构,这比排序数列使用更短的访问时间来插入、搜索和删除元素。

注意:在1990年,由William Pugh引入Skip List。

当你往map中插入数据时,它使用key来排序它们,所以,所有元素将是有序的。除了返回具体的元素,这个类也提供了获取map的子map的方法。

在这个指南中,你将学习如何使用ConcurrentSkipListMap类来实现一个通讯录的map。

准备工作…

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建一个Contact类。

1 public class Contact {

2.声明两个私有的、String类型的属性name和phone。

1 private String name;
2 private String phone;

3.实现这个类的构造器,并初始化它的属性。

1 public Contact(String name, String phone) {
2 this.name=name;
3 this.phone=phone;
4 }

4.实现返回name和phone属性值的方法。

1 public String getName() {
2 return name;
3 }
4 public String getPhone() {
5 return phone;
6 }

5.创建一个Task类,并指定它实现Runnable接口。

1 public class Task implements Runnable {

6.声明一个私有的、参数化为String类和Contact类的ConcurrentSkipListMap类型的属性map。

1 private ConcurrentSkipListMap<String, Contact> map;

7.声明一个私有的、String类型的属性id,用来存储当前任务的ID。

01 private String id;
02  
03 [/code[
04  
05 8.实现这个类的构造器,用来存储它的属性。
06  
07 1
08  
09 public Task (ConcurrentSkipListMap<String, Contact> map, String
10 id) {
11 this.id=id;
12 this.map=map;
13 }

9.实现run()方法。使用任务的ID和创建Contact对象的增长数,在map中存储1000个不同的通讯录。使用put()方法添加通讯录到map中。

1 @Override
2 public void run() {
3 for (int i=0; i<1000; i++) {
4 Contact contact=new Contact(id, String.valueOf(i+1000));
5 map.put(id+contact.getPhone(), contact);
6 }
7 }

10.通过创建Main类,并添加main()方法来实现这个例子的主类。

1 public class Main {
2 public static void main(String[] args) {

11.创建一个参数化为String类和Contact类的ConcurrentSkipListMap对象map。

1 ConcurrentSkipListMap<String, Contact> map;
2 map=new ConcurrentSkipListMap<>();

12.创建一个有25个Thread对象的数组,用来存储你将要执行的所有任务。

1 Thread threads[]=new Thread[25];
2 int counter=0;

13.创建和启动25个任务,对于每个任务指定一个大写字母作为ID。

1 for (char i='A'; i<'Z'; i++) {
2 Task task=new Task(map, String.valueOf(i));
3 threads[counter]=new Thread(task);
4 threads[counter].start();
5 counter++;
6 }

14.使用join()方法等待线程的结束。

1 for (int i=0; i<25; i++) {
2 try {
3 threads[i].join();
4 } catch (InterruptedException e) {
5 e.printStackTrace();
6 }
7 }

15.使用firstEntry()方法获取map的第一个实体,并将它的数据写入到控制台。

1 System.out.printf("Main: Size of the map: %d\n",map.size());
2 Map.Entry<String, Contact> element;
3 Contact contact;
4 element=map.firstEntry();
5 contact=element.getValue();
6 System.out.printf("Main: First Entry: %s: %s\n",contact.
7 getName(),contact.getPhone());

16.使用lastEntry()方法获取map的最后一个实体,并将它的数据写入到控制台。

1 element=map.lastEntry();
2 contact=element.getValue();
3 System.out.printf("Main: Last Entry: %s: %s\n",contact.
4 getName(),contact.getPhone());

17.使用subMap()方法获取map的子map,并将它们的数据写入到控制台。

01 System.out.printf("Main: Submap from A1996 to B1002: \n");
02 ConcurrentNavigableMap<String, Contact> submap=map.
03 subMap("A1996", "B1002");
04 do {
05 element=submap.pollFirstEntry();
06 if (element!=null) {
07 contact=element.getValue();
08 System.out.printf("%s: %s\n",contact.getName(),contact.
09 getPhone());
10 }
11 } while (element!=null);
12 }

它是如何工作的...

在这个指南中,我们已实现Task类来存储Contact对象到NavigableMap 中。每个通讯录都有一个名称(创建它的任务的ID的)和电话号码(1000到2000之间的数字)。我们已使用这些值的连续值作为通讯录的key。每个Task对象创建1000个通讯录,并使用put()方法将它们存储到NavigableMap中。

注意:如果你插入的key已存在,那么这个key的元素将被新的元素取代。

Main类的main()方法创建25个Task对象,并使用A-Z的字母作为IDs。然后,你已使用一些方法从map中获取数据。firstEntry()方法返回map第一个元素的Map.Entry对象,且不会删除这个元素。这个对象包含key和元素。你已调用getValue()方法来获取元素。你可以使用getKey()来获取元素的key。

lastEntry()方法返回map最后一个元素的Map.Entry对象,subMap()方法返回map的部分元素的ConcurrentNavigableMap对象。在这个例子中,元素拥有A1996到B1002之间的key。在这种情况下,你可以使用pollFirst()方法来处理subMap()方法返回的这些元素。这个方法将返回并删除submap中的第一个Map.Entry对象。

以下截图显示了程序执行的输出:

不止这些...

ConcurrentSkipListMap类有其他有趣的方法,这些方法如下:

  • headMap(K toKey):K是参数化ConcurrentSkipListMap对象的Key值的类。返回此映射的部分视图,其键值小于 toKey。
  • tailMap(K fromKey):K是参数化ConcurrentSkipListMap对象的Key值的类。返回此映射的部分视图,其键大于等于 fromKey。
  • putIfAbsent(K key, V Value):如果key不存在map中,则这个方法插入指定的key和value。
  • pollLastEntry():这个方法返回并删除map中最后一个元素的Map.Entry对象。
  • replace(K key, V Value):如果这个key存在map中,则这个方法将指定key的value替换成新的value。

参见

  • 在第6章,并发集合中的使用非阻塞线程安全的数列指南
时间: 2024-09-13 14:17:26

并发集合(六)使用线程安全的NavigableMap的相关文章

两个线程同时操作一个集合,一个线程读,一个线程写。有可能会产生并发问题吗?

问题描述 两个线程同时操作一个集合,一个线程读,一个线程写.有可能会产生并发问题吗? 我下面的代码为啥没有并发问题? 请哪位大神指导下 class Program { public Thread Threadone; public Thread Threadtwo; public event EventHandler EventRun; public static object obj=new object(); ArrayList ListArry = new ArrayList(); pri

并发集合(一)引言

声明:本文是< Java 7 Concurrency Cookbook >的第六章,作者: Javier Fernández González     译者:许巧辉 校对:方腾飞 在本章中,我们将包含: 使用非阻塞线程安全的列表 使用阻塞线程安全的列表 用优先级对使用阻塞线程安全的列表排序 使用线程安全的.带有延迟元素的列表 使用线程安全的NavigableMap 创建并发随机数 使用原子变量 使用原子数组 引言 在编程中,数据结构是一种基本的元素.几乎每个程序都使用一个或多个数据结构类型来存

Clojure的并发(六)Agent可以改进的地方

Clojure 的并发(一) Ref和STM Clojure 的并发(二)Write Skew分析Clojure 的并发(三)Atom.缓存和性能 Clojure 的并发(四)Agent深入分析和Actor Clojure 的并发(五)binding和let Clojure的并发(六)Agent可以改进的地方Clojure的并发(七)pmap.pvalues和pcallsClojure的并发(八)future.promise和线程     前面几节基本介绍Clojure对并发的支持,估计这个系列

C#并行编程-并发集合

原文:C#并行编程-并发集合 菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 背景 基于任务的程序设计.命令式数据并行和任务并行都要求能够支持并发更新的数组.列表和集合. 在.NET Framework 4 以前,为了让共享的数组.列表和集合能够被多个线程更新,需要添加复杂的代码来同步这些更新操作. 如您需要编写一个并行循环,这个循环以无序的方式向一个共享集合中添加元素,那么必须加入一个同步机制来保证这是一个线程安全的集合. System.Collenctions

C#常用算法:并发集合

微软对C#(4.0)的框架添加了全新的并发编程框架,现在我们也能用C#开发支持并发概念的程序的.在并发编程中最让人烦恼的应该就是如何数据同步:避免脏读和脏写,当然我们可以通过Lock技术来实现,也可以使用微软提供给我们的并发集合,这些集合都提供了TryDo方法.用它们对数据的读/写操作能在TryDo返回True的情况下执行.我们来看看它们吧: IProducerConsumerCollection 所有的并发集合都实现了这个接口,TryAdd和TryTake分别在读和写的时候判断是否能正常进行,

并发性-Web服务器的最大并发数和进程线程的关系疑问

问题描述 Web服务器的最大并发数和进程线程的关系疑问 刚才看了一篇关于进程线程的文章,一个进程会分配2G可用内存,一个线程默认会分配1M内存.那么一个进程最多能产生2000左右的线程数.那么一个web服务器(Tomcat)中一个请求过来,就会创建一个线程来处理,理论上不是只能最多处理2000并发请求了?而nginx好像可以处理更大并发(上万个).这是怎么实现的(不是每个请求生成一个线程?)?另外一个进程会分配2G可用内存是不是绝对的?还是只针对32位系统,64位是不是不止这个大小. 请高手指点

并发集合(三)使用阻塞线程安全的列表

使用阻塞线程安全的列表 列表(list)是最基本的集合.一个列表中的元素数量是不确定的,并且你可以添加.读取和删除任意位置上的元素.并发列表允许不同的线程在同一时刻对列表里的元素进行添加或删除,而不会产生任何数据不一致的问题. 在这个指南中,你将学习如何在你的并发应用程序中使用阻塞的列表.阻塞列表与非阻塞列表的主要区别是,阻塞列表有添加和删除元素的方法,如果由于列表已满或为空而导致这些操作不能立即进行,它们将阻塞调用的线程,直到这些操作可以进行.Java包含实现阻塞列表的LinkedBlocki

并发集合(二)使用非阻塞线程安全的列表

使用非阻塞线程安全的列表 列表(list)是最基本的集合.一个列表有不确定的元素数量,并且你可以添加.读取和删除任意位置上的元素.并发列表允许不同的线程在同一时刻对列表的元素进行添加或删除,而不会产生任何数据不一致(问题). 在这个指南中,你将学习如何在你的并发应用程序中使用非阻塞列表.非阻塞列表提供这些操作:如果操作不能立即完成(比如,你想要获取列表的元素而列表却是空的),它将根据这个操作抛出异常或返回null值.Java 7引进实现了非阻塞并发列表的ConcurrentLinkedDeque

《Java 7并发编程实战手册》第六章并发集合

由人民邮电出版社出版的<Java 7并发编程实战手册>终于出版了,译者是俞黎敏和申绍勇,该书将于近期上架.之前并发编程网组织翻译过此书,由于邮电出版社在并发网联系他们之前就找到了译者,所以没有采用并发网的译稿,但邮电出版社将于并发网展开合作,发布该书的样章(样章由并发网挑选,你也可以回帖告诉我们你想看哪一章的样章),并组织赠书活动回馈给活跃读者.活动详情请时刻关注并发网的微博和微信(微信号:ifeves),最后祝各位用餐愉快!:) 本章将介绍下列内容: 使用非阻塞式线程安全列表 使用阻塞式线程