使用线程安全的可操纵映射

Java 9并发编程指南 目录

ConcurrentNavigableMap是声明了Java API提供的数据结构接口,可以在并发程序中使用它。 实现ConcurrentNavigableMap接口的类将元素存储在两个部分中:

  • 唯一标识元素的
  • 声明元素的其它数据,称为

Java API也提供了实现ConcurrentSkipListMap的类,它是通过ConcurrentNavigableMap接口的行为实现非阻塞列表的接口,在内部使用跳跃表来存储数据。跳跃表是基于并行列表的数据结构,能够获得与二叉树类似的运行效率。 查看https://en.wikipedia.org/wiki/Skip_list获取更多关于跳跃表的信息。使用跳跃表可以得到一个排序的数据结构,而不是排序列表,且能更高效的插入、检索或删除元素。

跳跃表由William Pugh在1990年引入。

当向映射表中插入元素时,使用键来对元素排序,因此所有的元素都被序列化。键需要实现Comparable接口,或者向映射表的构造函数提供Comparator类 。除了返回具体元素的方法,此类还提供了获取映射的子映射方法。

本节将学习如何使用ConcurrentSkipListMap类实现联系人映射表。

准备工作

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

实现过程

通过如下步骤实现范例:

  1. 创建名为Contact的类:

    public class Contact {
    
  2. 声明两个名为name和phone的私有属性:

    	private final String name;
    	private final 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. 声明名为map的String和Contact类参数化的私有ConcurrentSkipListMap属性:

    	private final ConcurrentSkipListMap<String, Contact> map;
    
  7. 声明名为id的私有String属性,存储当前任务ID:

    	private final String id;
    
  8. 实现类构造函数,初始化属性:

    	public Task (ConcurrentSkipListMap<String, Contact> map,String id){
    		this.id=id;
    		this.map=map;
    	}
    
  9. 实现run()方法,使用任务ID和创建Contact对象的增量数在映射中存储1000个联系人。 使用put()方法存储映射表中的联系人:

    	@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. 创建名为map的String和Contact类参数化的私有ConcurrentSkipListMap属性:

    		ConcurrentSkipListMap<String, Contact> map = new ConcurrentSkipListMap<>();
    
  12. 创建包含26个Thread对象的数组,存储所有将要执行的Task对象:

    		Thread threads[]=new Thread[26];
    		int counter=0;
    
  13. 创建并启动26个任务对象,并为每个任务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 (Thread thread : threads){
    			try {
    					thread.join();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
  15. 使用firstEntry()方法得到映射表首个条目,输出数据到控制台:

    		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()方法得到映射表最后一个条目,输出数据到控制台:

    		element=map.lastEntry();
    		contact=element.getValue();
    		System.out.printf("Main: Last Entry: %s: %s\n", contact.getName(), contact.getPhone());
    
  17. 使用subMap()方法获得映射表的子映射,输出数据到控制台:

    		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对象。每个联系人都有一个名称,这是创建其任务的ID,以及一个电话号码,这个号码在1000到2000之间。将这些值连接为联系人的键,每个Task对象创建1000个联系人,使用put()方法将这些联系人存储到可控制映射中。

如果插入一个映射中已存在键的元素,那么与此键关联的元素将被新元素取代。

Main()类的main()方法使用大写字母A到Z作为ID创建26个Task对象,然后用一些方法从映射表中获取数据。firstEntry()方法返回包含映射表首个元素的Map.Entry对象,此方法不删除映射表元素。对象包括键和元素,调用getValue()方法获得此元素,可以使用getKey()方法得到元素的键值。

lastEntry()方法返回包含映射表最后一个元素的Map.Entry对象,此时元素的键在A1996到B1002之间。使用pollFirst()方法处理subMap()方法的元素。此方法返回且删除子映射的首个Map.Entry对象。

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

pics/07_03.jpg

扩展学习

ConcurrentSkipListMap类还有如下一些方法:

  • headMap(K toKey):这里K是在ConcurrentSkipListMap对象的参数化中使用的键值的类。此方法返回映射表首个元素的子映射,其中元素比作为参数传递的元素键值小。
  • tailMap(K fromKey):这里K是在ConcurrentSkipListMap对象的参数化中使用的键值的类。此方法返回映射表最后一个元素的子映射,其中元素比作为参数传递的元素键值大。
  • putIfAbsent(K key, V Value):此方法插入作为参数指定的值,如果映射中不存在则将其作为参数指定的键。
  • pollLastEntry():此方法返回且删除包含映射表最后一个元素的Map.Entry对象。
  • replace(K key, V Value):如果映射表中存在此键,此方法则替换与指定为参数的键相关联的值。

更多关注

  • 本章“使用非阻塞线程安全双端队列”小节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值