C#使用对象内容的线程锁定方式,而非使用引用的线程锁定方式(可以支持值类型作为锁定的资源,例如int等)

本文介绍了一种改进的C#线程同步机制,通过自定义MonitorLock类实现值类型和引用类型的线程锁定,解决了传统lock()语句无法有效处理值类型锁定的问题。通过示例展示了如何使用MonitorLock类进行线程同步,确保了多线程环境下数据的一致性和安全性。

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

一、介绍

在多线程中,每个线程都有自己的资源,但是有些数据是共享的,即每个线程都可以访问修改。这可能带来的问题就是几个线程同时执行一个数据,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

c#语言自带的lock(){}语句只能用于锁定引用类型,基于引用地址的锁定,但是如果遇到需要让值类型作为锁的业务情况【例如用户Id有可能是long类型】,也有可能是字符串类型需要作为锁定的值的时候【只有在一开始定义在代码中的字符串是存储在字符串常量区中的,如果在代码执行过程中生成的字符串是没有存储在字符串常量区中的,这样就会导致他们引用的地址不一样,从而不能达到线程同步的效果】

验证:执行过程中生成的字符串不是存在在字符串常量区中的
var str1 = "123";
var str2 = "123";
var str3 = 123.ToString();
//判断str1和str2字符串值是否相同
Console.WriteLine(str1 == str2);
//判断str1和str3字符串值是否相同
Console.WriteLine(str1 == str3);
Console.WriteLine("------------------");
//判断str1和str2字符串的引用地址是否一致
Console.WriteLine(object.ReferenceEquals(str1, str2));
//判断str1和str3字符串的引用地址是否一致
Console.WriteLine(object.ReferenceEquals(str1, str3));
Console.ReadKey();
结果
True
True
------------------
True
False
请按任意键继续. . .

为了使得这种情况也支持锁定,就得锁定的是对象的内容而非引用地址,当前组件实现的原理是:
锁定对象【值类型 OR 引用类型】->映射锁定对象【引用类型lockObject】->lock(lockObject)

注意:因为映射实现方式是字典,所以作为锁定的数据类型最好实现了GetHashCode()、Equals(object obj)两个方法

在这里插入图片描述

二、方法介绍

方法介绍
MonitorLock.Enter(T lockObject)锁定指定的资源,如果没有获取到此资源就一直阻塞当前线程等待资源释放
MonitorLock.TryEnter(T lockObject)尝试锁定指定资源,如果没有获取到就返回“假”,如果获取到就返回“真”
MonitorLock.TryEnter(T lockObject, int millisecondsTimeout)尝试锁定指定资源,尝试等待指定的毫秒数[在此时间内是阻塞当前线程的],如果都还没有获取到就返回“假”,如果在指定的时间内获取到就返回“真”
MonitorLock.Exit(T lockObject)释放当前资源的锁定,如果资源不是当前线程锁定的就会报错
MonitorLock.IsEntered(T lockObject)判断当前线程是否是否锁定了当前资源,他配合MonitorLock.Exit(oLock) 使用防止释放的时候出错
MonitorLock.Lock(T key)生成锁定对象 配合 using(){} 实现 lock(){}效果

三、实现代码

/// <summary>
/// 锁对象【根据对象值的锁,而非基于引用的锁】
/// </summary>
/// <typeparam name="T">锁的资源类型【引用类型】</typeparam>
public class MonitorLock<T> : IDisposable
{
    /// <summary>
    /// 锁值字典
    /// </summary>
    private static readonly ConcurrentDictionary<T, object> LockDictionary;

    static MonitorLock()
    {
        LockDictionary = new ConcurrentDictionary<T, object>();
    }

    /// <summary>
    /// 锁定指定的资源,如果没有获取到此资源就一直阻塞当前线程等待资源释放
    /// </summary>
    /// <param name="lockObject">锁定的对象</param>
    public static void Enter(T lockObject)
    {
        var obj = LockDictionary.GetOrAdd(lockObject, new object()); //得到或者添加当前Key对象对应的锁定对象
        Monitor.Enter(obj); //锁定当前对象
    }

    /// <summary>
    /// 尝试锁定指定资源,
    /// 如果没有获取到就返回“假”,
    /// 如果获取到就返回“真”
    /// </summary>
    /// <param name="lockObject">锁定的对象</param>
    /// <returns>是否获取锁成功</returns>
    public static bool TryEnter(T lockObject)
    {
        var obj = LockDictionary.GetOrAdd(lockObject, new object()); //得到或者添加当前Key对象对应的锁定对象
        return Monitor.TryEnter(obj); //锁定当前对象
    }

    /// <summary>
    /// 尝试锁定指定资源,
    /// 尝试等待指定的毫秒数[在此时间内是阻塞当前线程的],
    /// 如果都还没有获取到就返回“假”,
    /// 如果在指定的时间内获取到就返回“真”
    /// </summary>
    /// <param name="lockObject">锁定的对象</param>
    /// <param name="millisecondsTimeout">等待超时的时间(毫秒)</param>
    /// <returns>是否获取锁成功</returns>
    public static bool TryEnter(T lockObject, int millisecondsTimeout)
    {
        var obj = LockDictionary.GetOrAdd(lockObject, new object()); //得到或者添加当前Key对象对应的锁定对象
        return Monitor.TryEnter(obj, millisecondsTimeout); //锁定当前对象
    }

    /// <summary>
    /// 释放当前资源的锁定,如果资源不是当前线程锁定的就会报错
    /// </summary>
    /// <param name="lockObject">锁定的对象</param>
    public static void Exit(T lockObject)
    {
        object obj;
        if (!LockDictionary.TryGetValue(lockObject, out obj)) return; //得到当前key对应的锁定对象
        Monitor.Exit(obj); //解锁锁定对象
    }

    /// <summary>
    /// 判断当前线程是否是否锁定了当前资源,
    /// 他配合MonitorLock.Exit(oLock) 使用防止释放的时候出错
    /// </summary>
    /// <param name="lockObject">锁定的对象</param>
    /// <returns>当前线程是否是否锁定了当前资源</returns>
    public static bool IsEntered(T lockObject)
    {
        object obj;
        if (!LockDictionary.TryGetValue(lockObject, out obj)) return false; //得到当前key对应的锁定对象
        return Monitor.IsEntered(obj); //判断当前线程是否是否锁定了当前资源
    }

    /// <summary>
    ///  生成锁定对象 配合 using(){} 实现 lock(){}效果
    /// </summary>
    /// <param name="key">锁定的Key</param>
    /// <returns>锁定对象</returns>
    public static MonitorLock<T> Lock(T key)
    {
        return new MonitorLock<T>(key);
    }

    /// <summary>
    /// 锁定的Key
    /// </summary>
    private readonly T _key;

    /// <summary>
    /// 是否已经释放
    /// </summary>
    private bool _isDispose;

    /// <summary>
    /// 实例化锁对象
    /// </summary>
    /// <param name="key">锁定的键值</param>
    public MonitorLock(T key)
    {
        _key = key;
        Enter(key); //锁定
    }

    /// <summary>
    /// 释放锁
    /// </summary>
    public void Dispose()
    {
        if (_isDispose) return;
        Exit(_key); //解锁
        _isDispose = true;
    }
}

四、测试代码

1.锁定字符串例子(模拟锁定的值内容相同但是引用地址不相同的情况)
class Program
{
    static void Main(string[] args)
    {
        Test1();
        Test2();
        Console.ReadKey();
    }

    private static void Test1()
    {
        var str1 = "123";//存储在字符串常量区中的字符串
        var str3 = 123.ToString();//执行代码生成的字符串
        var count = 0;
        var t1 = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                //将str1字符串作为锁定的值
                lock (str1)
                {
                    count++;
                }
            }
        });
        var t2 = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                //将str3字符串作为锁定的值
                lock (str3)
                {
                    count++;
                }
            }
        });
        Task.WaitAll(t1, t2);
        Console.WriteLine($"使用Lock语句,结果{count},正确结果:{2000000},结果正确:{count == 2000000}");
    }


    private static void Test2()
    {
        var str1 = "123";//存储在字符串常量区中的字符串
        var str3 = 123.ToString();//执行代码生成的字符串
        var count = 0;
        var t1 = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                //将str1字符串作为锁定的值
                using (MonitorLock<string>.Lock(str1))
                {
                    count++;
                }
            }
        });
        var t2 = Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                //将str3字符串作为锁定的值
                using (MonitorLock<string>.Lock(str3))
                {
                    count++;
                }
            }
        });
        Task.WaitAll(t1, t2);
        Console.WriteLine($"使用MonitorLock<string>.Lock,结果{count},正确结果:{2000000},结果正确:{count == 2000000}");
    }
}
输出结果
使用Lock语句,结果1149203,正确结果:2000000,结果正确:False
使用MonitorLock<string>.Lock,结果2000000,正确结果:2000000,结果正确:True
请按任意键继续. . .
MonitorLock<string>.Enter(str);//锁定str
.....
MonitorLock<string>.Exit(str);//解锁str
//等价于
using (MonitorLock<string>.Lock(str))
{
    ....
}
2.使用值类型作为锁定值
var count = 0;
var t1 = Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 1000000; i++)
    {
        //使用int类型的1024作为锁定值
        using (MonitorLock<int>.Lock(1024))
        {
            count++;
        }
    }
});
var t2 = Task.Factory.StartNew(() =>
{
    for (int i = 0; i < 1000000; i++)
    {
        //使用int类型的1024作为锁定值
        using (MonitorLock<int>.Lock(1024))
        {
            count++;
        }
    }
});
Task.WaitAll(t1, t2);
Console.WriteLine($"使用MonitorLock<int>.Lock,结果{count},正确结果:{2000000},结果正确:{count == 2000000}");

结果
使用MonitorLock<int>.Lock,结果2000000,正确结果:2000000,结果正确:True
请按任意键继续. . .
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值