悲观锁和乐观锁是两种常见的并发控制策略。悲观锁假定并发访问会导致冲突,因此在访问共享资源时采用独占机制(如互斥锁),以确保同一时刻只有一个线程能够对资源进行修改;乐观锁则假定并发访问不会冲突,因此在访问共享资源时不加锁,而是在更新数据前先检查数据是否被其他线程修改过。
下面分别给出 C# 中悲观锁和乐观锁的示例代码:
悲观锁
下面的示例代码演示了如何使用 lock 关键字来实现悲观锁:
using System;
using System.Threading.Tasks;
class Program
{
static int counter = 0;
static object lockObj = new object();
static void Main(string[] args)
{
Task t1 = Task.Run(() => IncrementCounter());
Task t2 = Task.Run(() => IncrementCounter());
Task.WaitAll(t1, t2);
Console.WriteLine($"Final counter value: {counter}");
}
static void IncrementCounter()
{
for (int i = 0; i < 100000; i++)
{
lock (lockObj) // 使用 lock 关键字获取锁
{
int temp = counter;
temp++;
counter = temp;
}
}
}
}
乐观锁
using System;
using System.Threading.Tasks;
using System.Threading;
class Program
{
static int counter = 0;
**C#接单交流群452760896**
static void Main(string[] args)
{
Task t1 = Task.Run(() => IncrementCounter());
Task t2 = Task.Run(() => IncrementCounter());
Task.WaitAll(t1, t2);
Console.WriteLine($"Final counter value: {counter}");
}
static void IncrementCounter()
{
for (int i = 0; i < 100000; i++)
{
int temp = Interlocked.CompareExchange(ref counter, 0, 0);
while (temp != counter)
{
temp = Interlocked.CompareExchange(ref counter, temp + 1, temp);
}
}
}
}
在这个示例代码中,我们使用了 Interlocked.CompareExchange()
方法来实现乐观锁。这个方法可以比较两个值是否相等,如果相等,则将第一个值替换为第三个值;否则返回第一个值。在这个例子中,我们将原子比较操作的结果存储在临时变量 temp
中,并不断检查 temp
是否等于当前的计数器值,如果不相等则再次调用 Interlocked.CompareExchange()
,直到成功为止。
需要注意的是,乐观锁的实现方式并不适用于所有场景。当并发访问的概率很高时,乐观锁可能会导致大量的重试和性能下降。此外,在更新数据时还需要考虑数据版本控制等问题。
乐观锁和悲观锁是常用的并发控制机制,其主要作用是保证多个线程对共享资源的访问安全。一般来说,乐观锁适用于读操作较多、写操作较少的场景,而悲观锁适用于写操作较多、读操作较少的场景。具体来说:
乐观锁:通常在读多写少的情况下使用,对于竞争不激烈的数据访问场景比较适合,例如缓存、计数器等。乐观锁的实现方式通常是在操作前先检查资源是否被其他线程修改过,如果没有则进行操作,否则抛出异常或者重试。常见的乐观锁技术包括版本号、时间戳等。
悲观锁:通常在写多读少的情况下使用,对于竞争激烈的数据访问场景比较适合,例如数据库、文件系统等。悲观锁的实现方式通常是在操作前先获取资源的锁定,其他线程需要等待当前线程释放锁定后才能进行操作。常见的悲观锁技术包括互斥锁、读写锁等。
总的来说,乐观锁和悲观锁各有优缺点,具体使用哪种锁取决于应用场景和需求。在实践中,乐观锁的使用频率可能更高一些,因为很多应用场景下,读操作的并发性要求比较高,而且乐观锁相对悲观锁来说实现起来可以更简单、高效。不过,在写操作比较频繁或者竞争比较激烈的情况下,悲观锁可能更适合一些。