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