并发集合框架
同步框架(sysnchronized collections)和并发框架(concurrent collections)的区别。
java5在同步的集合框架方面做了一些改进,提供了几个并发集合类。同步集合类通过序列化对集合状态的访问来保证线程安全。这种方法的代价就是降低并发性;当多个线程竞争这个集合的锁的时候,吞吐量自然降低了。
而并发集合类就是专门被设计出来实现多线程并发访问的。JAVA5增加了ConcurrentHashMap,用来替代基于hash的MAP实现,而CopyOnWriteArrayList是用来替换同步的List的--当遍历是一种常用操作的时候。新的ConcurrentMap接口添加了对一些常用的组合操作的支持,比如put-if-absent,replace,conditional remove。
使用并发集合替换同步集合,会大幅提高应用的伸缩性,而风险却很低。
JAVA5还新添加了两个新的集合类型,Queue和BlockingQueue.Queue呢,是在里面元素等待处理的过程中,将元素的集合进行加锁。该接口默认提供了几个实现,包括ConcurrentLinkedQueue,一个传统的FIFO队列;PriorityQueue,一个按优先级排序的队列。队列的操作是非阻塞的;如果一个队列是空的,那么获取操作将返回null。你可以将队列的行为当作List来看。实际上,LinkedList也实现了Queue接口,队列类不能进行随机的访问,但是却能有效的增加并发访问量。
BlockingQueue扩展了Queue,添加了阻塞的插入和获取操作。如果队列是空的,那么获取操作将阻塞,直到有一个元素存在为止;同时如果一个队列是null,那么一个插入操作将被阻塞直到有空间为止。阻塞操作在生产者-消费者这种设计中非常的有用,以后进行详述。
JAVA6添加了ConcurrentSkipListMap和ConcurrentSkipListSet两个类,用作同步的SortedMap或者SortedSet的替代品。
[b]ConcurrentHashMap[/b]
同步的集合类会在集合的每个操作执行期间持有一个锁。而有些操作,比如HashMap.get或者List.contains,可能比他表面看起来调用更多的操作,比如在遍历一个hash bucket或者List,去查找某个对象的时候,就必须调用他们各自的equals方法(而这些方法本身可能还需要调用别的操作)。在一个基于hash的集合中,如果hashCode没有很好的获得,那么元素的分布将会遇到很多冲突;在最坏的情况下,一个很烂的hash算法可能导致一个hash table变成一个linked list。遍历一个很长的list并在他们上调用equals方法,这需要消耗很长时间,而在这段时间中,其他线程将不能访问该集合。
ConcurrentHashMap 和HashMap是类似的,是基于hash的,但是他使用了一个完全不同的加锁策略,从而提供了更好的并发性和伸缩性。这种加锁策略不是对每个方法进行加锁,限制任意时刻只能单线程访问,他使用了更细粒度的加锁方法,叫做lock striping(姑且叫做锁分离吧),允许更大自由度的访问。任何的读取线程都可以并发访问的map,而且,读取线程和写入线程可并发的访问map,但是只有有限数量的写线程允许并发修改。这么作的结果就是在并发访问下具有更高的吞吐量,而比单线程访问几乎没有性能损失。
ConcurrentHashMap以及其他并发集合,比同步的集合做了些改进,迭代器不再抛出ConcurrentModificationException异常,因此也不再需要在迭代过程中将集合加锁了。ConcurrentHashMap返回的是不稳定的迭代器而不是一个快速失败。一个不稳定的迭代器允许并发修改,并且在迭代过程中,有可能会体现出其他线程的修改(可能但不保证)。
与这些改进一起的,还有一些妥协和折衷。一些对整个Map进行的操作,比如size和isEmpty方法,削弱了对并发集合的反应。由于size()的结果在他被计算出来之后会过期,所以仅仅是个估计值,所以size返回的值是个近似值,而不是精确值。第一次看到这些,可能会比较迷惑,而在真实世界中,像size()和isEmpty()这些方法在并发环境中作用被大大削弱。
有些特性是同步的集合拥有,而并发集合没有的,如对整个map的加锁,保持排他的访问。对于Hashtable和synchronizedMap来说,需要一个map锁,来保证其他线程不能同时访问改对象。这些特性在有些情况下可能是必要的,比如:原子的添加好几个mapping,或者遍历map多次,抑或着需要查看到相同的元素并保持相同的状态。总得来说,这算是比较合理的权衡和妥协:毕竟并发集合本应该就是能被并发的修改。
比起Hashtable和synchronizedMap来说,他的优点很多而缺点很少,所以用来作为Map的实现的替代,在大部分情况下会得到更好的可伸缩性,只有当你的应用需要锁定整个map的时候,ConcurrentHashMap才不适用。
[b]其他原子map操作[/b]
由于ConcurrentHashMap不能被锁住,并保持排他的访问,所以,我们不能使用客户端锁来创建一个原子操作,比如put-if-absent。所以ConcurrentHashMap接口定义了put-if-absent,remove-if-equal,replace-if-equal等原子操作,如果你发现自己给已存在的同步集合添加了这些实现,那么似乎就意味着你可能需要用ConcurrentMap来替换你的类。
[b]CopyOnWriteArrayList[/b]
CopyOnWriteArrayList是并发集合提供的用以替代同步的List的实现类,在绝大部分情况下,由于他不需要在整个迭代过程中加锁,所以可以提供更好的并发性能。
为什么会有这种copy-on-write的集合出现呢,大家貌似还应该记得,不可变对象如果被正确的发布出来,那么他是不需要任何的锁来控制访问的,所以copy-on-write就是从这里获得的灵感,这种集合当有任何修改时,就会创建并重新发布出一个新的集合。对于这种集合来说他不会抛出ConcurrentModificationException异常,返回的元素是当迭代器创建的时候持有的元素,而没有后续的更改。很显然的是,每次更改都会创建一个新的集合出来,这是有开销的,特别是当集合很大的时候,所以这种集合的适用场合应该是读取比较频繁,而修改操作较少的场合。
同步框架(sysnchronized collections)和并发框架(concurrent collections)的区别。
java5在同步的集合框架方面做了一些改进,提供了几个并发集合类。同步集合类通过序列化对集合状态的访问来保证线程安全。这种方法的代价就是降低并发性;当多个线程竞争这个集合的锁的时候,吞吐量自然降低了。
而并发集合类就是专门被设计出来实现多线程并发访问的。JAVA5增加了ConcurrentHashMap,用来替代基于hash的MAP实现,而CopyOnWriteArrayList是用来替换同步的List的--当遍历是一种常用操作的时候。新的ConcurrentMap接口添加了对一些常用的组合操作的支持,比如put-if-absent,replace,conditional remove。
使用并发集合替换同步集合,会大幅提高应用的伸缩性,而风险却很低。
JAVA5还新添加了两个新的集合类型,Queue和BlockingQueue.Queue呢,是在里面元素等待处理的过程中,将元素的集合进行加锁。该接口默认提供了几个实现,包括ConcurrentLinkedQueue,一个传统的FIFO队列;PriorityQueue,一个按优先级排序的队列。队列的操作是非阻塞的;如果一个队列是空的,那么获取操作将返回null。你可以将队列的行为当作List来看。实际上,LinkedList也实现了Queue接口,队列类不能进行随机的访问,但是却能有效的增加并发访问量。
BlockingQueue扩展了Queue,添加了阻塞的插入和获取操作。如果队列是空的,那么获取操作将阻塞,直到有一个元素存在为止;同时如果一个队列是null,那么一个插入操作将被阻塞直到有空间为止。阻塞操作在生产者-消费者这种设计中非常的有用,以后进行详述。
JAVA6添加了ConcurrentSkipListMap和ConcurrentSkipListSet两个类,用作同步的SortedMap或者SortedSet的替代品。
[b]ConcurrentHashMap[/b]
同步的集合类会在集合的每个操作执行期间持有一个锁。而有些操作,比如HashMap.get或者List.contains,可能比他表面看起来调用更多的操作,比如在遍历一个hash bucket或者List,去查找某个对象的时候,就必须调用他们各自的equals方法(而这些方法本身可能还需要调用别的操作)。在一个基于hash的集合中,如果hashCode没有很好的获得,那么元素的分布将会遇到很多冲突;在最坏的情况下,一个很烂的hash算法可能导致一个hash table变成一个linked list。遍历一个很长的list并在他们上调用equals方法,这需要消耗很长时间,而在这段时间中,其他线程将不能访问该集合。
ConcurrentHashMap 和HashMap是类似的,是基于hash的,但是他使用了一个完全不同的加锁策略,从而提供了更好的并发性和伸缩性。这种加锁策略不是对每个方法进行加锁,限制任意时刻只能单线程访问,他使用了更细粒度的加锁方法,叫做lock striping(姑且叫做锁分离吧),允许更大自由度的访问。任何的读取线程都可以并发访问的map,而且,读取线程和写入线程可并发的访问map,但是只有有限数量的写线程允许并发修改。这么作的结果就是在并发访问下具有更高的吞吐量,而比单线程访问几乎没有性能损失。
ConcurrentHashMap以及其他并发集合,比同步的集合做了些改进,迭代器不再抛出ConcurrentModificationException异常,因此也不再需要在迭代过程中将集合加锁了。ConcurrentHashMap返回的是不稳定的迭代器而不是一个快速失败。一个不稳定的迭代器允许并发修改,并且在迭代过程中,有可能会体现出其他线程的修改(可能但不保证)。
与这些改进一起的,还有一些妥协和折衷。一些对整个Map进行的操作,比如size和isEmpty方法,削弱了对并发集合的反应。由于size()的结果在他被计算出来之后会过期,所以仅仅是个估计值,所以size返回的值是个近似值,而不是精确值。第一次看到这些,可能会比较迷惑,而在真实世界中,像size()和isEmpty()这些方法在并发环境中作用被大大削弱。
有些特性是同步的集合拥有,而并发集合没有的,如对整个map的加锁,保持排他的访问。对于Hashtable和synchronizedMap来说,需要一个map锁,来保证其他线程不能同时访问改对象。这些特性在有些情况下可能是必要的,比如:原子的添加好几个mapping,或者遍历map多次,抑或着需要查看到相同的元素并保持相同的状态。总得来说,这算是比较合理的权衡和妥协:毕竟并发集合本应该就是能被并发的修改。
比起Hashtable和synchronizedMap来说,他的优点很多而缺点很少,所以用来作为Map的实现的替代,在大部分情况下会得到更好的可伸缩性,只有当你的应用需要锁定整个map的时候,ConcurrentHashMap才不适用。
[b]其他原子map操作[/b]
由于ConcurrentHashMap不能被锁住,并保持排他的访问,所以,我们不能使用客户端锁来创建一个原子操作,比如put-if-absent。所以ConcurrentHashMap接口定义了put-if-absent,remove-if-equal,replace-if-equal等原子操作,如果你发现自己给已存在的同步集合添加了这些实现,那么似乎就意味着你可能需要用ConcurrentMap来替换你的类。
[b]CopyOnWriteArrayList[/b]
CopyOnWriteArrayList是并发集合提供的用以替代同步的List的实现类,在绝大部分情况下,由于他不需要在整个迭代过程中加锁,所以可以提供更好的并发性能。
为什么会有这种copy-on-write的集合出现呢,大家貌似还应该记得,不可变对象如果被正确的发布出来,那么他是不需要任何的锁来控制访问的,所以copy-on-write就是从这里获得的灵感,这种集合当有任何修改时,就会创建并重新发布出一个新的集合。对于这种集合来说他不会抛出ConcurrentModificationException异常,返回的元素是当迭代器创建的时候持有的元素,而没有后续的更改。很显然的是,每次更改都会创建一个新的集合出来,这是有开销的,特别是当集合很大的时候,所以这种集合的适用场合应该是读取比较频繁,而修改操作较少的场合。