Java同步容器、并发容器、阻塞队列浅谈

同步容器类:Vector、Hashtable等。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。
同步容器类都是安全的,但在某些情况下可能需要客户端加锁来保护复合操作。容器上常见的复合操作包括:迭代、跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算,例如 若没有则添加。在同步容器类中,这些操作在没有客户端加锁的情况下仍然是线层安全的,但当其他线程并发的修改容器时,它们可能会出现意料之外的行为。
同步容器类迭代器表现出的行为是 fast-fail(及时失败)的,这意味着:当它们发现容器在迭代过程被修改时,就会抛出一个ConcurrentModificationException异常。
实现方式是:将计数器的变化与容器关联起来:如果在迭代期间计数器被修改,那么hasNext或next将抛出ConcurrentModificationException。单线程中当对象直接从容器中修改数据结构而不是通过Iterator来修改时,就会抛出这个异常。
隐藏迭代器:某些情况下,迭代器会被隐式调用。如:
private final Set<Integer> set=new HashSet<Integer>(); 
public synchronized add(Integer i) { set.add(i);}
public void addTenTings(){
Random r=new Random();
for(int i=0;i<10;i++)     add(r.nextInt());
System.out.println("ABC"+set);
}
编译器将字符串的连接操作转换为调用StringBuilder.append(Object),而这个方法又会调用容器的toString方法,标准容器的toString方法将迭代容器,并在每个元素上调用toString来生成容器内容的格式化表示。
addTenThings方法可能会抛出ConcurrentModificationException,因为在使用println的set之前必须获取类的锁。
并发容器:ConcurrentHashMap用来替代同步且基于散列的Map。CopyOnWriteArrayList用于在遍历操作为主要操作的情况下替代同步的List。在新的ConcurrentMap接口中增加了对一些常见的复合操作的支持,例如“若没有则增加”、替换以及有条件删除等。
BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作一直等待,直到队列中出现一个可用的元素。同样的,如果队列已满(对于有界的队列来说),那么插入元素的操作将一直等待,直到队列中出现可用的空间。在“生产者-消费者”这种设计模式中,阻塞队列是非常有用的。
正如ConcurrentHashMap用于代替基于散列的同步Map,Java 6也引入了ConcurrentSkipListMap和ConcurrentSkipListSet,分别作为同步的SortedMap和SortedSet的并发替代品。

ConcurrentHashMap与其他并发容器一起增强了同步容器类:它们提供的迭代器不会抛出ConcurrentModificationException,因此不需要再迭代过程中对容器加锁。ConcurrentHashMap返回的迭代器具有弱一致性,而并非“及时失败”。若一致性的迭代器可以容忍并发的修改,当穿件迭代器时会遍历已有的元素,并可以(但不保证)将修改操作返回给容器。

“写入时复制”容器:CopyOnWriteArrayList用于替代同步List,在某些情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制。(类似的,CopyOnWirteArraySet的作用是替代同步Set)。
“写入时复制”容器的线程安全性在于:只要正确的发布一个事实不可变的对象,那么在访问该对象时就不再需要进一步的同步。
在每次修改时都会创建并重新发布一个新的容器副本,从而实现可变性。当线程需要修改容器时,会将容器数组复制一份副本,在副本中修改完成后将容器中array的引用指向修改后的数组。所以在此过程中读写分离。因为当线程在对容器进行读或者写的操作时,操作的是不同的容器。“写入时复制”容器返回的迭代器不会抛出ConcurrentModificationException。显然,每当修改容器时都会复制底层数组,这需要一定的开销。 仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器

阻塞队列:BlockingQueue,BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作一直等待,直到队列中出现一个可用的元素。同样的,如果队列已满(对于有界的队列来说),那么插入元素的操作将一直等待,直到队列中出现可用的空间。在“生产者-消费者”这种设计模式中,阻塞队列是非常有用的。在类库中包含了BlockingQueue的多种实现,期中,LinkedBlockingQueue和ArrayBlockingQueue是FIFO队列。PriorityBlockingQueue是一个按优先级排序的队列,当你希望按照某种顺序而不是FIFO来处理元素时,这个队列将非常有用。最后一个BlockingQueue实现是SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。它的实现方式是直接交付工作,降低了将数据从生产者移动到消费者的延迟。因为SynchronousQueue没有存储功能,因此put和take会一直阻塞,直到有另一个线程已经尊卑好参与到交付过程中。 仅当有足够多的消费者,并且总是有一个消费者准备好获取交付的工作时,才适合使用同步队列。
双端队列与工作密取: Java 6增加了两种容器类型,Deque和BlockingDeque,它们分别对Queue和BlockingQueue进行了扩展。Deque是一个双端队列,实现了在队列头和队列尾的高效插入和移除。具体实现包括ArrayDeque和LinkedBlockingDeuqe。
  工作密取。在工作密取设计中,每隔消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者的 双端队列末尾秘密地获取工作。双端队列适用于工作密取模式。工作密取非常适用于既是消费者也是生产者问题——当执行某个工作时可能导致出现更多的工作。        当一个工作线程找到新的任务单元时,它会将其放到自己队列的末尾(或者在工作共享设计模式中,放入其他工作者线程的队列中)。当双端队列为空时,它会在另一个线程的队列队尾查找新的任务,从而确保每隔线程都保持忙碌状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值