一、为什么这种形式不能实现线程安全性?
剖析一段代码:

package com.guor.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {

public List<E> list = Collections.synchronizedList(new ArrayList<E>());public synchronized boolean putIfAbsent(E x){    boolean absent = !list.contains(x);    if(absent){        list.add(x);    }    return absent;}

}
毕竟putIfAbsent曾经申明了synchronized 类型的变量,对不对?问题在于在谬误的锁上进行了同步。无论list应用哪一个锁来爱护它的状态,能够确定的是,这个锁并不是ListHelper上的锁。ListHelper只是带来了同步的假象,只管所有的链表操作都被申明为synchronized,但却应用了不同的锁,这意味着putIfAbsent绝对于List的其它操作来说并不是原子的,因而就无奈确保当putIfAbsent执行时另一个线程不会批改链表。
要想使这个办法能正确执行,必须使List在实现客户端加锁或内部加锁时应用同一个锁。客户端加锁是指,对于应用某个对象X的客户端代码,应用X自身用于保护器状态的锁来爱护这段客户端代码。要应用客户端加锁,你必须晓得对象X应用的是哪一个锁。
在Vector和同步封装器的文档中指出,它们通过应用Vector或封装器的内置锁来反对客户端加锁,上面代码能够实现线程平安的list操作。

public boolean putIfAbent(E x){

synchronized (list){    boolean absent = !list.contains(x);    if(absent){        list.add(x);    }    return absent;}

}

二、组合
当为现有的类增加一个原子操作时,有一种更好的办法:组合。
上面代码中ImprovedList通过将List对象的操作委托给底层的List实例来实现List的操作,同时还增加了一个原子的putIfAbsent办法。(与Collections.synchronizedList和其它容器封装器一样,ImprovedList假如把某个链表对象传给构造函数当前,客户代码不会再间接应用这个对象,而只能通过ImprovedList来拜访它。)

package com.guor.util;

import java.util.List;

public class ImprovedList<T> implements List<T> {

private final List<T> list;public ImprovedList(List<T> list){    this.list = list;}public synchronized boolean putIfAbsent(T x){    boolean absent = !list.contains(x);    if(absent){        list.add(x);    }    return absent;}public synchronized void clear(){    list.clear();}...

}

ImprovedList通过本身的内置锁减少了一层额定的加锁,它并不关怀底层的list是否是线程平安的,即便List不是线程平安的或者批改了它的加锁实现,ImprovedList也会提供统一的加锁机制来实现线程安全性。尽管额定的同步层可能导致轻微的性能损失,但与模仿另一个对象的加锁策略相比,ImprovedList更为强壮。事实上,咱们应用了Java监视器模式来封装现有的List,并且只有在类中领有指向底层List的惟一内部援用,就能确保线程安全性。

三、同步容器类
同步容器类包含Vector和Hashtable,这些实现线程平安的形式是:将它们的状态封装起来,并对每个私有办法都进行同步,使得每次只有一个线程能拜访容器的状态。
同步容器类都是线程平安的,但在某些状况下可能须要额定的客户端加锁来爱护合乎操作。容器上常见的复合操作包含:迭代、跳转以及条件运算,例如“若没有则增加”。在同步容器类中,这些合乎操作在没有客户端加锁的状况下依然是线程平安的,但当其余线程并发地批改容器时,它们可能会体现出意料之外的行为。

四、暗藏迭代器
尽管加锁能够避免迭代器抛出ConcurrentModificationException,但你必须要记住所有对共享容器进行迭代的中央都须要加锁。理论状况要更加简单,因为在某些状况下,迭代器会暗藏起来。比方log.info("set content is :"+set),编译器将字符串的连贯操作转换为调用StringBuilder.append(Object),而这个办法又会调用容器的toString办法,规范容器的toString办法将迭代容器,并在每个元素上调用toString来生成容器内容的格式化示意。
容器的hashCode和equals等办法也会间接地执行迭代操作,当容器作为另一个容器的元素和键值时,就会呈现这种状况。

五、并发容器
jdk1.5提供了多种并发容器来改良同步容器的性能。同步容器将所有对容器状态的拜访都串行化,以实现它们的线程安全性。这种办法的代价是重大升高并发性,当多个线程竞争容器的锁时,吞吐量将重大升高。
另一方面,并发容器是针对多个线程并发拜访设计的。在jdk1.5中减少了ConcurrentHashMap,用来代替同步且基于散列的Map以及CopyOnWriteArrayList,用于在遍历操作为次要操作的状况下代替同步的List。在新的ConcurrentMap接口中减少了对一些常见复合操作的反对,例如“若没有则增加”、替换以及有条件删除等。
通过并发容器来代替同步容器,能够极大地提高伸缩性并升高危险。
jdk1.5减少了两种新的容器类型:Queue和BlockingQueue。Queue用来长期保留一组期待解决的元素。它提供了几种实现,包含ConcurrentLinkedQueue,这是一个传统的先进先出队列,以及PriorityQueue,这是一个非并发的优先队列。Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。尽管能够用List来模仿Queue的行为,事实上,正是通过LinkedList来实现Queue的,但还须要一个Queue的类,因为它能去掉List的随机拜访需要,从而实现更高效的并发。
BlockingQueue扩大了Queue,减少了可阻塞的插入和获取等操作,如果队列为空,那么获取元素的操作将始终阻塞,直到队列中呈现一个可用的元素。如果队列已满,那么插入元素的操作将始终阻塞,直到队列中呈现可用的空间。
同步容器在执行每个操作期间都持有一个锁。

六、ConcurrentHashMap
与HashMap一样,ConcurrentHashMap也是一个基于散列的Map,但它应用了一种齐全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap并不是将每个办法都在同一个锁上同步并使得每次只能有一个线程拜访容器,而是应用一种更细的加锁机制来实现更大程度的共享,这种机制成为分段锁。在这种机制中,任意数量的读取线程能够并发地拜访Map,执行读取操作的线程和执行写入操作的线程能够并发地拜访Map,并且肯定数量的写入线程能够并发地批改Map。ConcurrentHashMap带来的后果是,在并发拜访环境下将实现更高的吞吐量,而在单线程环境中只损失十分小的性能。
ConcurrentHashMap返回的迭代用具有弱一致性,而并非“及时失败”。弱一致性的迭代器能够容忍并发的批改,当创立迭代器时会遍历已有的元素,并能够在迭代器被结构后将批改操作反映给容器。