【转】Java多线程-同步集合和并发集合

转载出处:https://blog.csdn.net/yuruixin_china/article/details/82082195,
http://youyu4.iteye.com/blog/2352846

同步集合可以简单地理解为通过synchronized来实现同步的集合。如果有多个线程调用同步集合的方法,它们将会串行执行。

arrayList和vector、stack

  • Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比
  • ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍
  • Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数
  • Stack是继承于Vector,基于动态数组实现的一个线程安全的栈
  • arrayList、vector、Stack的共性特点:随机访问速度快,插入和移除性能较差(这是数组的特点,三者的底层均为数组实现)

HashMap和Hashtable

  • HashMap是非synchronized的,而Hashtable是synchronized的。这说明Hashtable是线程安全的,而且多个线程可以共享一个Hashtable
  • 由于Hashtable是线程安全的,也是synchronized的,所以在单线程环境下比HashMap要慢
  • HashMap可以存在null的键值(key)和值(value), 但是Hashtable是不可以的

Collections

Collections是为集合提供各种方便操作的工具类,通过它,可以实现集合排序、查找、替换、同步控制、设置不可变集合

Collections.synchronizedCollection(Collectiont)
Collections.synchronizedList(Listlist)
Collections.synchronizedMap(Mapmap)
Collections.synchronizedSet(Set t)

上面几个方法是Collections工具类将集合变为同步集合,从而解决集合的线程安全问题.

同步集合在单线程的环境下能够保证线程安全,但是通过synchronized同步方法将访问操作串行化,导致并发环境下效率低下。而且同步集合在多线程环境下的复合操作(迭代、条件运算如没有则添加等)是非线程安全,需要客户端代码来实现加锁。

并发集合是jdk5.0重要的特性,增加了并发包java.util.concurrent.*。Java内存模型、volatile变量及AbstractQueuedSynchronizer(简称AQS同步器),是并发包众多实现的基础。

常见的并发集合:

  • ConcurrentHashMap:线程安全的HashMap的实现
  • CopyOnWriteArrayList:线程安全且在读操作时无锁的ArrayList
  • CopyOnWriteArraySet:基于CopyOnWriteArrayList,不添加重复元素
  • ArrayBlockingQueue:基于数组、先进先出、线程安全,可实现指定时间的阻塞读写,并且容量可以限制
  • LinkedBlockingQueue:基于链表实现,读写各用一把锁,在高并发读写操作都多的情况下,性能优于ArrayBlockingQueue

CopyOnWrite集合即写时复制的集合

通俗的理解是当我们往一个集合添加元素的时候,不直接往当前集合添加,而是先将当前集合进行Copy,复制出一个新的集合,然后新的集合里添加元素,添加完元素之后,再将原集合的引用指向新的集合。这样做的好处是我们可以对CopyOnWrite集合进行并发的读,而不需要加锁,因为当前集合不会添加任何元素。所以CopyOnWrite集合也是一种读写分离的思想,读和写不同的集合。

CopyOnWrite的应用场景

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

示例代码

package com.ifeve.book;  

import java.util.Map;  
import com.ifeve.book.forkjoin.CopyOnWriteMap;  

/** 
 * 黑名单服务 
 * 
 * @author fangtengfei 
 * 
 */  
public class BlackListServiceImpl {  

    private static CopyOnWriteMap blackListMap = new CopyOnWriteMap(  
            1000);  

    public static boolean isBlackList(String id) {  
        return blackListMap.get(id) == null ? false : true;  
    }  

    public static void addBlackList(String id) {  
        blackListMap.put(id, Boolean.TRUE);  
    }  

    /** 
     * 批量添加黑名单 
     * 
     * @param ids 
     */  
    public static void addBlackList(Map ids) {  
        blackListMap.putAll(ids);  
    }  
}  

注意两点:

  • 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
  • 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。

CopyOnWrite的缺点

内存占用问题

因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

数据一致性问题

CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

你可能感兴趣的:(Java高级)