多线程一些收藏

高并发场景线程安全的List

2022-03-04  本文已影响0人  小胖学编程

为什么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<Integer> r2 = new CopyOnWriteArrayList<>();
//使用sync代码块装饰传入List的读写操作(读写均加锁)
List<String> r3 = Collections.synchronizedList(new ArrayList<>());

4. 并发安全的案例

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

public class TestList {

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

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

推荐阅读

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

上一篇 下一篇

猜你喜欢

热点阅读