线程安全的集合

本文探讨了在多线程环境中使用线程安全的集合类的重要性,如通过Collections.synchronizedList和Collections.synchronizedMap实现的同步集合,以及更高效的并发集合如ConcurrentHashMap、ConcurrentSkipListMap和ConcurrentLinkedQueue。这些集合通过复杂算法最小化竞争,提供弱一致性迭代器,避免ConcurrentModificationException异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在多线程中,如果要并发的修改一个数据结构,那么很有可能会破坏这个数据结构。例如,一个线程可能要向一个散列表中插入一个元素,假如在调整各个桶之间的链接关系时被剥夺了控制权,而此时正好有另外一个线程正在遍历链表,则可能会产生异常或者死循环。

可以通过锁来保护共享的数据结构,但是选择线程安全的实现作为替代可能更容易一些。

一、旧的线程安全的集合

任何集合类都可以通过使用同步包装器变成线程安全的:

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());

Map<K,V> synchMap = Collections.synchronizedList(new HasMap<K,V>());

结果集合的方法使用锁加以保护,提供线程安全的访问。

如果在另一个线程可能进行修改时要对集合进行迭代,任然需要使用封锁。

synchronized(synchHashMap)
{
    Iterator<K> iter = synchHashMap.keySet().iterator();
    while(iter.hasNext())
        //遍历
}

如果使用for each 循环必须使用同样的代码,因为循环使用了迭代器。如果在迭代的过程中另一个线程修改集合,迭代器会失效,抛出ConcurrentModificationException异常,因此并发的修改可以被可靠的检测出来。

二、高效的映像、集合和队列

java.util.concurrent包提供了映像、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentLinkedQueue。这些集合通过复杂的算法,通过允许并发的访问数据结构的不同部分来使竞争极小化。

这些集合返回弱一致性的迭代器。这意味着迭代器不一定能反映出他们被构造之后的所有的修改,但是,他们不会将同一个值返回两次,也不会抛出ConcurrentModificationException的异常。

例如下面的例子:

import java.util.*;
import java.util.concurrent.*;

/*
 *   ConcurrentLinkedQueue是“线程安全”的队列,而LinkedList是非线程安全的。
 *
 *   下面是“多个线程同时操作并且遍历queue”的示例
 *   (01) 当queue是ConcurrentLinkedQueue对象时,程序能正常运行。
 *   (02) 当queue是LinkedList对象时,程序会产生ConcurrentModificationException异常。
 *
 * @author skywang
 */
public class Main {

    // TODO: queue是LinkedList对象时,程序会出错。
    //private static Queue<String> queue = new LinkedList<String>();
    private static Queue<String> queue = new ConcurrentLinkedQueue<String>();
    public static void main(String[] args) {

        // 同时启动两个线程对queue进行操作!
        new MyThread("ta").start();
        new MyThread("tb").start();
    }

    private static void printAll() {
        String value;
        Iterator iter = queue.iterator();
        while(iter.hasNext()) {
            value = (String)iter.next();
            System.out.print(value+", ");
        }
        System.out.println();
    }

    private static class MyThread extends Thread {
        MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
                int i = 0;
            while (i++ < 6) {
                // “线程名” + "-" + "序号"
                String val = Thread.currentThread().getName()+i;
                queue.add(val);
                // 通过“Iterator”遍历queue。
                printAll();
            }
        }
    }
}

使用ConcurrentLinkedQueue时不会报错
在这里插入图片描述
使用LinkedList时产生错误:
在这里插入图片描述

### Java线程安全集合类及其实现方式 #### 1. 集合框架中的线程安全性 Java集合框架中并非所有的集合都默认提供线程安全保障。一些常见的集合类如`ArrayList`、`LinkedList`和`HashMap`等是非线程安全的[^2]。为了在多线程环境中使用这些集合而不引发数据一致性问题,开发者可以选择不同的策略来实现线程安全。 #### 2. 同步包装器 Java标准库提供了通过`Collections`工具类的方法将非线程安全集合转换为线程安全版本的功能。例如: - `Collections.synchronizedList(List<T> list)` 可以将一个普通的列表转为线程安全的列表。 - `Collections.synchronizedSet(Set<T> set)` 将集合同步化。 - `Collections.synchronizedMap(Map<K,V> map)` 对映射表进行同步处理。 这种方法虽然简单易用,但在高并发场景下性能可能较差,因为每次访问都需要加锁解锁操作[^3]。 ```java // 使用同步包装器创建线程安全的 List 和 Map 示例 List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>()); ``` #### 3. 并发包 (java.util.concurrent) 中的线程安全集合 除了上述基本的同步封装外,JDK还引入了一个专门用于解决高性能并发需求的包——`java.util.concurrent`。该包包含了多种针对特定用途优化过的线程安全集合类,比如: - **CopyOnWriteArrayList**: 当读取频率远高于修改频率时非常有效;它的工作原理是在写入时复制整个数组副本并更新新副本的内容后再替换旧副本[^4]。 ```java // 创建一个线程安全的可变长度数组 List<String> threadSafeList = new CopyOnWriteArrayList<>(); ``` - **ConcurrentHashMap**: 提供了高度优化的数据结构设计,允许更高的并发级别而无需完全锁定整个哈希表。其内部采用了分段锁技术(Segmented Locking),即把整个散列划分为若干个小部分分别上锁管理,从而减少争用情况下的等待时间[^1]。 ```java // 初始化 ConcurrentHashMap 实例 Map<Integer, String> concurrentMap = new ConcurrentHashMap<>(); // 插入键值对 concurrentMap.put(1, "Value"); // 获取指定 key 的 value 值 String result = concurrentMap.get(1); ``` - **BlockingQueue 接口及其具体实现**:适用于生产者消费者模式的应用场合。像 LinkedBlockingQueue 或 ArrayBlockingQueue 这样的队列实现了阻塞功能,在尝试向已满或者为空的队列执行插入/删除动作失败时会自动挂起当前线程直到条件满足为止。 ```java BlockingQueue<String> queue = new LinkedBlockingQueue<>(10); try { queue.put("Message"); // 如果队列满了,则此调用会被阻塞直至空间可用 } catch (InterruptedException e) {} ``` 综上所述,对于不同类型的业务逻辑以及具体的性能考量因素而言,选择合适的线程安全集合至关重要。如果只是偶尔发生少量竞争状况的话,那么简单的同步机制或许已经足够应付局面;然而一旦涉及到频繁存取且规模较大的共享资源情形之下,则应该优先考虑采用更先进的解决方案诸如那些来自 java.util.concurrent 包内的组件们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值