高并发场景线程安全的List

为什么Vector和Collections.SynchronizedList的get方法要加锁呢?

1. 线程不安全的ArrayList

为什么说ArrayList是线程不安全的:

  1. add()操作抛出数组越界异常;
  2. add()操作会丢失元素;
  3. set()操作去修改元素,get()操作去获取元素时,可以读到新值也可能读到旧值,无法保证一致性。

源码分析:

//存放list集合元素的数组,默认容量10
transient Object[] elementData; 
//list大小
private int size;

add()的源码:

public boolean add(E e) {
    //确定添加元素之后,集合的大小是否足够,若不够则会进行扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //插入元素
    elementData[size++] = e;
    return true;
}

场景1:多个线程都没进行扩容,但是执行了elementData[size++] = e;时,便会出现“数组越界异常”;
场景2:因为size++本身就是非原子性的,多个线程之间访问冲突,这时候两个线程可能对同一个位置赋值,就会出现“size小于期望值的结果”;

2. Vector和Collections.SynchronizedList的get方法要加锁呢?

get()操作时集合中的元素不能并发的被修改,否则就易出现数据问题。

  1. Vector和Collections.SynchronizedList的get方法加了synchronized后可以保证顺序性与实时一致性,当一个线程在读取数据时,一定可以看到其他线程解锁前写入的全部数据。
  2. 并且Vector和Collections.SynchronizedList的数组并没有用volatile修饰,如果不加锁,也无法保证可见性。

3. 线程安全的3种List集合

//方法上使用sync关键字(读写均加锁)
Vector vector = new Vector();
//写操作每一次均copy一个数组,读操作不加锁(写加锁性能低,读不加锁性能极高)
CopyOnWriteArrayList r2 = new CopyOnWriteArrayList<>();
//使用sync代码块装饰传入List的读写操作(读写均加锁)
List r3 = Collections.synchronizedList(new ArrayList<>());
  • Vector/Collections.synchronizedList:读写均加锁,来实现线程安全;
  • CopyOnWriteArrayList基于写时复制技术实现的,读操作无锁(读取快照),写操作有锁,体现了读写分离的思想,但是无法提供实时一致性。

4. 并发安全的案例

下面给出一个案例,即容易出现并发问题的场景:

public class TestList {

    private static final ExecutorService VIEW_EXECUTOR = new ThreadPoolExecutor(2,4,1000,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));

    /**
     * 目前比较常用的构建线程安全的List有三种方法:
     * 

* 使用Vector容器 * 使用Collections的静态方法synchronizedList(List< T> list) * 采用CopyOnWriteArrayList容器 */ public static void main(String[] args) { //常用方式:使用线程池并发处理,填充结果 ArrayList res = new ArrayList<>(); CompletableFuture.runAsync(() -> { //todo 逻辑处理 //线程不安全,需要使用一个线程安全的List,这里推荐Collections.synchronizedList res.add("success"); }, VIEW_EXECUTOR); } }

推荐阅读

重学Java并发编程(写时复制技术在CopyOnWriteArrayList中的应用)

你可能感兴趣的:(高并发场景线程安全的List)