并发集合(六)使用线程安全的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对象。

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

4

不止这些...

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章,并发集合中的使用非阻塞线程安全的数列指南
目录
相关文章
|
22天前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
8天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
8天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
12天前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
26 0
|
20天前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
17 2
|
22天前
|
安全 Java
【Java集合类面试十三】、HashMap如何实现线程安全?
实现HashMap线程安全的方法包括使用Hashtable类、ConcurrentHashMap,或通过Collections工具类将HashMap包装成线程安全的Map。
|
22天前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
22天前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
11天前
|
开发框架 Android开发 iOS开发
跨平台开发的双重奏:Xamarin在不同规模项目中的实战表现与成功故事解析
【8月更文挑战第31天】在移动应用开发领域,选择合适的开发框架至关重要。Xamarin作为一款基于.NET的跨平台解决方案,凭借其独特的代码共享和快速迭代能力,赢得了广泛青睐。本文通过两个案例对比展示Xamarin的优势:一是初创公司利用Xamarin.Forms快速开发出适用于Android和iOS的应用;二是大型企业借助Xamarin实现高性能的原生应用体验及稳定的后端支持。无论是资源有限的小型企业还是需求复杂的大公司,Xamarin均能提供高效灵活的解决方案,彰显其在跨平台开发领域的强大实力。
19 0
|
22天前
|
安全 Java
【Java集合类面试五】、 如何得到一个线程安全的Map?
如何得到一个线程安全的Map的方法包括:使用Collections工具类将Map包装为线程安全,使用java.util.concurrent包下的ConcurrentHashMap,以及不推荐使用性能较差的Hashtable。