线程安全的List和Set

List

Vector

对于可能产生线程安全问题的方法,Vector在底层都是使用synchronized关键字来修饰这些方法,从而保证多线程下的线程安全。但是这样做会有很大的性能问题,因为每一个操作都会将整个容器都锁起来,同一时刻最多只能有一个线程在操作该容器,性能低下。

Collections.synchronizedList()

调用这个方法其实是使用装饰者模式对List进行了一次包装,给他添加了一个对象锁,然后在调用方法的时候会先用synchronized对这个对象上锁,在性能上与Vector差不多。

CopyOnWriteArrayList

CopyOnWriteArrayList采用了写时复制(COW)的思想,在对数组进行修改时,会拷贝原数组,然后在新数组上进行修改,并替换原数组。它使用锁(内部组合了ReentrantLock) + 数组拷贝 + volatile(底层数组array使用volatile来修饰,保证写时复制完成后其它线程可见)来保证线程安全。CopyOnWriteArrayList的实现中,读取时不需要加锁,只有修改时需要加锁,而且修改时需要拷贝数组,性能较差,所以CopyOnWriteArrayList适用于读多写少的情景。并且,由于在修改时是对新数组进行修改,接着替换引用,那么在并发状态下,读线程可能会读到旧数据只能保证数据的最终一致性,不能保证数据的实时一致性)。

add操作:在对数据进行操作之前会先进行加锁操作(使用内部组合的ReentrantLock的lock()方法),这样就保证了只有一个线程在对容器进行修改操作,然后创建新数组(新数组的大小是不多也不少的),然后将原数组内容拷贝到新数组,再在新数组上完成数组添加操作,最后使用新数组替换引用,然后解锁。其它修改操作类似,也是先加锁,然后创建新数组,拷贝原数组到新数组,在新数组上完成修改,最后替换引用并解锁。

get操作:get操作是无锁的,先拿到数组引用table,然后根据索引下标定位元素,最后返回对应的值。但是,在获取到数组引用后,定位并返回数组元素前,数组table可能发生了变化,这时获取到的就不是最新值,不过get操作获取到的是在其开始执行那一刻的最新值。

迭代器:通过iterator方法创建的迭代器,传入的是底层数组的引用,那么在迭代过程中,如果产生了修改,因为使用了COW技术,是由一个新的数组替换了老的数组的引用,但是此时,迭代器内部仍然使用的是老数组,这个数组并不会发生修改,所以不会有ConcurrentModification异常。

Set

Collections.synchronizedSet()

调用这个方法其实是使用装饰者模式对Set进行了一次包装,给他添加了一个对象锁,然后在调用方法的时候会先用synchronized对这个对象上锁,这样的话就会把整个Set锁上,同一时刻只会有一个线程在操作容器,效率低。

CopyOnWriteArraySet

CopyOnWriteArraySet内部组合了一个CopyOnWriteArrayList,CopyOnWriteArraySet的各种操作其实是委派给CopyOnWriteArrayList来实现的,只不过保证了容器内没有重复元素。

Guess you like

Origin blog.csdn.net/zhang_qing_yun/article/details/119145793