在 C# 中,List<T>
不是线程安全的。以下是原因及解决方案的详细讨论。
1. 为什么 List<T>
不是线程安全的?
-
多线程访问的问题:
List<T>
是为单线程环境设计的。如果多个线程同时对同一个List<T>
进行操作(如添加、删除、修改等),可能会导致数据竞态条件(Race Condition)或其他未定义行为。- 比如,一个线程正在扩展
List<T>
的容量(Add
操作),而另一个线程正在读取列表中的元素,就可能引发异常或返回错误的结果。
-
未使用锁或其他同步机制:
List<T>
的内部实现没有任何锁机制,也不支持并发控制。因此,无法保证操作的原子性。
-
容量管理问题:
List<T>
在容量不足时会动态扩容。多线程环境下同时触发扩容可能会导致数据丢失或程序崩溃。
2. 如何解决 List<T>
的线程安全问题?
- 手动加锁:
- 使用
lock
关键字对共享的List<T>
操作进行同步,确保线程安全。
- 使用
示例代码:
using System;
using System.Collections.Generic;
using System.Threading;
class Program
{
static List<int> sharedList = new List<int>();
static readonly object lockObject = new object();
static void AddItem(int item)
{
lock (lockObject) // 加锁确保线程安全
{
sharedList.Add(item);
}
}
static void Main()
{
Thread thread1 = new Thread(() => AddItem(1));
Thread thread2 = new Thread(() => AddItem(2));
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
lock (lockObject)
{
Console.WriteLine(string.Join(", ", sharedList));
}
}
}
- 使用线程安全的集合:
- 使用 .NET 提供的线程安全集合类,如
ConcurrentBag<T>
、ConcurrentQueue<T>
或ConcurrentDictionary<TKey, TValue>
。 - 如果需要类似
List<T>
的线程安全版本,可以使用ConcurrentBag<T>
。
- 使用 .NET 提供的线程安全集合类,如
示例代码:
using System;
using System.Collections.Concurrent;
using System.Threading;
class Program
{
static ConcurrentBag<int> safeList = new ConcurrentBag<int>();
static void AddItem(int item)
{
safeList.Add(item);
}
static void Main()
{
Thread thread1 = new Thread(() => AddItem(1));
Thread thread2 = new Thread(() => AddItem(2));
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
foreach (var item in safeList)
{
Console.WriteLine(item);
}
}
}
- 同步的包装类:
- 使用
Collections.Synchronized
方法为非线程安全集合添加同步功能。
- 使用
示例代码:
using System;
using System.Collections;
class Program
{
static void Main()
{
IList synchronizedList = ArrayList.Synchronized(new ArrayList());
lock (synchronizedList.SyncRoot) // 手动同步
{
synchronizedList.Add(1);
synchronizedList.Add(2);
}
foreach (var item in synchronizedList)
{
Console.WriteLine(item);
}
}
}
3. List<T>
在多线程中的常见问题
-
异常:
如果多个线程同时修改List<T>
,可能抛出以下异常:InvalidOperationException
IndexOutOfRangeException
- 数据损坏或未定义行为
-
数据竞争:
数据可能在读写之间被其他线程修改,导致错误或不一致的结果。
4. 选择线程安全集合的依据
集合类型 | 适用场景 |
---|---|
List<T> | 单线程操作场景 |
ConcurrentBag<T> | 需要线程安全的无序集合操作 |
ConcurrentQueue<T> | 需要线程安全的先进先出(FIFO)队列 |
ConcurrentStack<T> | 需要线程安全的后进先出(LIFO)栈 |
ConcurrentDictionary<TKey, TValue> | 线程安全的键值对存储与访问 |
总结
List<T>
默认不是线程安全的。它适用于单线程环境或对线程安全性无要求的场景。- 解决方案:
- 手动使用
lock
加锁。 - 替换为线程安全的集合(如
ConcurrentBag<T>
)。 - 根据场景选择更合适的数据结构。
- 手动使用