List线程不安全问题和写时复制(CopyOnWrite)

面试题:ArrayLIst线程不安全,请编写一个线程不安全的案例,并给出解决方案

public class ListDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        /**
         * 1. 故障出现
         *      java.util.ConcurrentModificationException
         *
         * 2. 导致原因
         *      并发争抢修改导致
         *
         * 3. 解决方案:
         *      List<String> list = new Vector<>();
         *      List<String> list = Collections.synchronizedList(new ArrayList<>());
         *      List<String> list = new CopyOnWriteArrayList<>();
         */
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }).start();
        }
    }

}

上面的代码线程不安全,在多个线程同时给list做修改时,会抛出java.util.ConcurrentModificationException,解决方案代码注释已经写明,其中最优的方式则是使用java.util.concurrent.CopyOnWriteArrayList这个类,即写时复制。

写时复制:CopyOnWrite容器即写时复制容器,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是现将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将容器的引用指向新的容器 setArray(newElements)。这样子的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite是一种读写分离的思想,读和写在不容的容器中进行。

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;
    
    // 定义了一把锁,在写入的时候使用
    final transient ReentrantLock lock = new ReentrantLock();

    // 容器对象,volatile关键字,使得所有线程可见
    private transient volatile Object[] array;

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	// 获取容器list
            Object[] elements = getArray();
            int len = elements.length;
            // 复制了一份,并且长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 写入到容器中
            newElements[len] = e;
            // 重组指向引用
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

	// get方法直接从容器中获取,没有加锁
	private E get(Object[] a, int index) {
        return (E) a[index];
    }
    
}

<think>好的,用户现在问的是线程安全的集合有哪些。首先,我需要确定用户可能的背景使用场景。通常,这样的问题来自正在学习多线程编程的开发者,可能在使用Java或者其他支持多线程的语言遇到了集合的线程安全问题。用户可能想了解在同编程语言中如何选择线程安全的集合,或者在实际项目中如何避免并发问题。 接下来,我需要回想常见编程语言中的线程安全集合。Java是这类问题最常见的语言,所以首先应该涵盖Java中的线程安全集合,比如Vector、Hashtable,以及Concurrent包下的类如ConcurrentHashMap、CopyOnWriteArrayList等。同,用户可能对现代并发工具感兴趣,比如Java的Collections.synchronizedListBlockingQueue等。 然后,考虑其他语言如C#Python中的线程安全集合。例如,C#中的ConcurrentDictionary、BlockingCollection,Python中的queue.Queuemultiprocessing模块中的结构。这能帮助用户跨语言应用知识。 用户可能没有明确说明他们的具体需求,比如是哪种语言,或者特定场景下的使用,但提供多语言的示例可以覆盖更广的受众。此外,需要解释为什么某些集合是线程安全的,比如通过锁机制或CAS操作,这样用户能理解背后的原理,选择合适的集合。 还要注意区分传统线程安全集合现代的高性能并发集合。例如,Java的VectorHashtable是早期的实现,而ConcurrentHashMap使用了分段锁或CAS来提升性能。指出这些区别可以帮助用户避免过的选择,采用更高效的方案。 同,用户可能有性能方面的考虑,比如在高并发环境下如何选择低竞争的集合结构。需要提到无锁数据结构或基于CAS的实现,比如ConcurrentLinkedQueue,适用于高并发场景。 最后,确保回答结构清晰,分点列出同语言中的线程安全集合,并给出使用建议注意事项,帮助用户根据具体需求做出选择。可能还需要提醒用户线程安全集合的正确用法,比如迭代器的显式同步,避免常见的并发陷阱。</think>以下是同编程语言中常见的线程安全集合及使用建议,按技术分类整理: ### Java中的线程安全集合 #### 1. 传统同步集合 $$Vector<String> syncList = new Vector<>();$$ $$Hashtable<String, Integer> syncMap = new Hashtable<>();$$ - **实现原理**:通过`synchronized`关键字实现方法级同步 - **缺点**:性能较差(锁粒度大) - **替代方案**:建议使用`Collections.synchronizedList()`包装普通集合 #### 2. 并发包集合(java.util.concurrent) $$ConcurrentHashMap<String, Object> concurrentMap = new ConcurrentHashMap<>();$$ $$CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();$$ $$BlockingQueue<String> bq = new LinkedBlockingQueue<>();$$ - **特性**: - `ConcurrentHashMap`采用分段锁/CAS(JDK8+) - `CopyOnWriteArrayList`使用复制技术 - `BlockingQueue`实现生产-消费模型 #### 3. 原子类集合 $$AtomicReferenceArray<Integer> atomicArray = new AtomicReferenceArray<>(10);$$ - **适用场景**:需要原子性操作的简单数据结构 ### C#中的线程安全集合 $$ConcurrentDictionary<string, int> cd = new ConcurrentDictionary<string, int>();$$ $$BlockingCollection<string> bc = new BlockingCollection<string>();$$ - **特性**: - `ConcurrentBag<T>`适合对象池场景 - `ImmutableCollections`提供可变集合 ### Python中的线程安全集合 ```python from queue import Queue safe_queue = Queue() ``` - **注意**: - 列表/字典原生非线程安全 - 多进程场景推荐`multiprocessing.Queue` - 协程场景推荐`asyncio.Queue` ### 通用实现原理 $$lock.acquire() \rightarrow modify \rightarrow lock.release()$$ $$CAS(expect, update)$$ - **同步方式**: 1. 悲观锁(synchronized/ReentrantLock) 2. 乐观锁(CAS操作) 3. 读锁(ReadWriteLock) ### 使用建议 1. **性能优先**:选择无锁数据结构(如`ConcurrentLinkedQueue`) 2. **少读多**:使用`CopyOnWrite`系列集合 3. **数据一致性**:优先选择可变集合 4. **特殊场景**: - 定任务推荐`DelayQueue` - 数据统计推荐`ConcurrentSkipListMap` ### 注意事项 1. 复合操作仍需显式同步: ```java // 错误示例 if(!map.containsKey(key)) { map.put(key, value); // 非原子操作 } // 正确法 map.putIfAbsent(key, value); // 原子方法 ``` 2. 迭代器需要显式同步 3. 注意内存可见性问题(使用volatile或原子类) 实际选择需根据具体场景进行压力测试,高并发系统建议优先考虑无锁数据结构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值