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

简介:

声明:本文是《 Java 7 Concurrency Cookbook 》的第六章,作者: Javier Fernández González     译者:许巧辉 校对:方腾飞

使用线程安全的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类。

public class Contact {

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

private String name;
private String phone;

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

public Contact(String name, String phone) {
this.name=name;
this.phone=phone;
}

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

public String getName() {
return name;
}
public String getPhone() {
return phone;
}

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

public class Task implements Runnable {

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

private ConcurrentSkipListMap<String, Contact> map;

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

private String id;

[/code[

8.实现这个类的构造器,用来存储它的属性。



public Task (ConcurrentSkipListMap<String, Contact> map, String
id) {
this.id=id;
this.map=map;
}

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

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

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

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

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

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

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

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

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

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

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

for (int i=0; i<25; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

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

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

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

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

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

System.out.printf("Main: Submap from A1996 to B1002: \n");
ConcurrentNavigableMap<String, Contact> submap=map.
subMap("A1996", "B1002");
do {
element=submap.pollFirstEntry();
if (element!=null) {
contact=element.getValue();
System.out.printf("%s: %s\n",contact.getName(),contact.
getPhone());
}
} while (element!=null);
}

它是如何工作的...

在这个指南中,我们已实现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。

参见

目录
相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
245 0
|
1月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
209 59
|
1月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
43 6
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
61 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
38 1
|
7天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
25 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1