介绍java5和java6中引入的基础模块。包括同步容器类,并发容器类,同步工具,阻塞队列等。
1、同步容器类
同步容器类包括:主要是Vector和HashTable,还有一些功能相似的类(java1.2中),由Collections.synchronizedXxx等工厂方法创建。
同步容器类都是线程安全的类,实现线程安全的方式是,将他们的状态都封装起来,并对每个公有的方法,都进行同步,使得每次只有一个线程能够访问容器的装态。
同步容器类虽然是线程安全的,但是有时候,对于复合操作,还是需要通过额外的客户端加锁来保护。
比如说Vector中getlast()和deletelast(),先获取size(),然后get或者remove(size()-1)
public static Object getLast(Vector<Object> e){ int lastindex =e.size()-1; return e.get(lastindex); } public static void deletLast(Vector<Object> e){ int lastIndex = e.size()-1; e.remove(lastIndex); }
这样多个线程调用时,可能会出现,getlast方法,先get之前获取size(),然后deletlast删掉最后一个元素,size发生变化,这样getlast(size()-1)出现数组下标越界异常。因此要通过加锁来完成。
public static Object getLast(Vector<Object> e){ synchronized (e) { int lastindex =e.size()-1; return e.get(lastindex); } } public static void deletLast(Vector<Object> e){ synchronized (e) { int lastIndex = e.size()-1; e.remove(lastIndex); } }
迭代器
迭代器,容器中直接迭代或者foreach循环,若果并发的修改容器的话,会表现出及时失败。也就是收,当容器在迭代的过程中发现被修改,会立刻抛出ConcurrentModificationException。
要想避免出现这个异常,就需要在迭代过程中加锁,但是这样一来,又会带来新的问题---并发性能差,或者导致死锁等问题。即使不产生死锁,也会导致程序吞吐量降低CPU利用率降低等问题。
这样另一个解决方法是,克隆容器,这样容器就被封闭在线程内,其他线程就无法在迭代期间对其修改。当然,克隆也有一定的代价,比如空间,操作等效率。
可以综合实际的要求,选择加锁或者是克隆容器方法。
隐藏的迭代器:
很多时候,我们牢记在共享容器迭代的时候需要加锁,不然会出现CME异常。但是实际会比较复杂,存在隐藏的迭代器不易被发现,如String s ="字符串" +"字符的联结"+容器;
编译器对字符串的链接操作,会执行Stringbuilder的append方法,然后调用标准容器的tostring(),而标准容器的tostring方法将迭代容器,并在每一个元素上调用toString来生成容器内容的格式化表示。
容器的hashcode和equals方法,同样也会间接的执行迭代,当容器作为另一个容器的键值或者元素时,就会出现。containsAll removeAll retainAll等方法,以及把容器作为参数的构造函数,都会对容器进行迭代。都有可能抛出CurrentModificationException异常。
2.并发容器类
同步容器实质上是加锁,也就是对容器的状态访问进行串行化,并发效率低。通过使用并发容器,可以极大地提高伸缩性并降低风险。
CurrentHashMap
使用分段锁(一种粒度更细的锁),任意数量的读取线程可以并发的访问Map,并且只有一定数量的写入操作可以并发的修改map,CurrentHashMap带来的是在并发环境下吞吐量更高,单线程下只损失非常小的性能。
currentMAp没有实现对map加锁而提供独占访问。
CopyOnWriteArrayList(写入时复制)
代替同步的List,在某些情况下提供了更好的并发,并且迭代期间不需要对容器进行加锁或者复制(CopyOnWriteSet代替set)
写入时复制容器的安全性在于:只要正确的发布一个事实不可变的对象,那么在访问该对象时,就不需要再进一步的同步。每次修改时,都会创建并且发布一个新的容器副本。从而实现可变性。
仅当迭代操作远远多于修改操作时才应该使用写入时复制容器。
BlockingQueue
提供可阻塞的put和take方法,以及支持定时offer和poll,如果队列满,则put阻塞到有空间可用,若队列为空,则take阻塞到有元素可用。
阻塞队列支持生产者和消费者模式,数据生成时,生产者将数据放入队列,在消费者准备处理数据时,将从队列中获取数据。生成者和消费者不需要知道对方的情况,只需要操作队列。