《C#多线程编程实战》1.10 lock关键字

lock关键字是锁定资源用的。

书上的代码解释很好。

   /// <summary>
/// 抽象类 加减法
/// </summary>
abstract class CounterBase
{
/// <summary>
///抽象 加法 方法
/// </summary>
public abstract void Increment(); /// <summary>
/// 抽象 减法 方法
/// </summary>
public abstract void Decrement();
}
/// <summary>
/// 不使用lock关键字的实现抽象类Counter
/// </summary>
class Counter : CounterBase
{
public int Count { get; private set; }
/// <summary>
/// 非Lock关键字减法
/// </summary>
public override void Decrement()
{
Count--;
}
/// <summary>
/// 非lock关键字加法
/// </summary>
public override void Increment()
{
Count++;
}
}
/// <summary>
/// 使用Lock关键字的类
/// </summary>
class CounterWidthLock : CounterBase
{
/// <summary>
/// 判断是否锁定资源
/// </summary>
public readonly object _syncRoot = new object(); public int Count { get; private set; } /// <summary>
/// Lock关键字减法
/// </summary>
public override void Decrement()
{
lock (_syncRoot)//lock关键字,锁定资源
{
Count--;
}
}
/// <summary>
/// lock关键字加法
/// </summary>
public override void Increment()
{
lock (_syncRoot)//lock关键字,锁定资源
{
Count++;
}
}
}
/// <summary>
/// 测试主类
/// </summary>
class Program
{
/// <summary>
/// 测试主程序入口
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("不使用Lock关键字 Counter类"); var NotLockClass = new Counter(); var t1 = new Thread(() =>TestCounter(NotLockClass)); var t2 = new Thread(() => TestCounter(NotLockClass)); var t3= new Thread(() => TestCounter(NotLockClass)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"最终输出结果是{NotLockClass.Count}"); Console.ReadKey();
}
static void TestCounter(CounterBase counterBase)
{
for(int i=;i<;i++)
{
counterBase.Increment();//加法 counterBase.Decrement();//减法
}
}
}

上面这一部分是,未使用lock关键字的代码。

结果也如同书上说count是未定。多次启动程序 结果也是不一样的。正确的结果应该是0,加一次见一次正好分别是5K次。

《C#多线程编程实战》1.10 lock关键字《C#多线程编程实战》1.10 lock关键字

仔细观察代码,得到这样子的结果也是肯定的。

首先:

           var NotLockClass = new Counter();

            var t1 = new Thread(() =>TestCounter(NotLockClass));

            var t2 = new Thread(() => TestCounter(NotLockClass));

            var t3= new Thread(() => TestCounter(NotLockClass));

这个部分,是三个实例的Thread共享了一个Counter的实例化对象。

那么,要肯定的是这个Counter是一个引用类型

那么引用类型发挥在哪里呢?

            t1.Start();

            t2.Start();

            t3.Start();

            t1.Join();

            t2.Join();

            t3.Join();    

这里。

首先是T1 T2 T3三个线程启动。

然后是等待线程完成。

让结果变得不固定的原因就在这里。

我们先把方法TestCounter改造一下

   static void TestCounter(CounterBase counterBase)
{
for(int i=;i<;i++)
{
counterBase.Increment();//加法
Console.WriteLine($"加法启动次序{i}");
counterBase.Decrement();//减法
Console.WriteLine($"减法启动次序{i}");
}
}

加入一下两个控制台输出语句。

然后启动程序。

《C#多线程编程实战》1.10 lock关键字

会发现有很多重复的顺序。而这个就是资源抢夺。

为了更清楚的看执行的过程,我们再来改造一下。

 static void TestCounter(Counter counter)
{
for(int i=;i<;i++)
{
counter.Increment();//加法
Console.WriteLine($"加法启动次序{i},当前结果是{counter.Count}");
counter.Decrement();//减法
Console.WriteLine($"减法启动次序{i},当前结果是{counter.Count}");
}
}

之后在启动

《C#多线程编程实战》1.10 lock关键字

资源抢夺还是很严重的。 不过你们有没有发现。方法改造之后的结果和正确结果很相近了。不是-1,就是-2.这是一个很有意思的现象。

如果你将代码改造成:

class Counter : CounterBase
{
public int Count { get; private set; }
/// <summary>
/// 非Lock关键字减法
/// </summary>
public override void Decrement()
{
Console.WriteLine($"减法 当前{Count}");
Count--;
}
/// <summary>
/// 非lock关键字加法
/// </summary>
public override void Increment()
{
Console.WriteLine($"加法 当前{Count}");
Count++;
}
}

结果:

《C#多线程编程实战》1.10 lock关键字《C#多线程编程实战》1.10 lock关键字《C#多线程编程实战》1.10 lock关键字《C#多线程编程实战》1.10 lock关键字

竟然会出现正确答案,而且几率很高。得到-1这个答案我运行了很多次。-2更是看运气。

为什么会出现这个现象。我觉得是可以程序的方法可运行时间有一定关系吧。时间可能是多了一下。资源调度上面可能会分配。也可能是线程的原因。这个问题真的很有意思。

不过还是先放放。

我们依旧是得到未使用lock关键字,资源抢夺很严重!

那么我们来看看是使用lock关键字的部分。

改造代码:

 static void Main(string[] args)
{
Console.WriteLine("使用Lock关键字 CounterWidthLock类"); var UseLockClass = new CounterWidthLock(); var t1 = new Thread(() =>TestCounter(UseLockClass)); var t2 = new Thread(() => TestCounter(UseLockClass)); var t3= new Thread(() => TestCounter(UseLockClass)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine($"最终输出结果是{UseLockClass.Count}"); Console.ReadKey();
}

《C#多线程编程实战》1.10 lock关键字

肯定是正确答案。

我们改造一下代码:和上一样:

 static void TestCounter(CounterBase counterBase)
{
for(int i=;i<;i++)
{
counterBase.Increment();//加法
Console.WriteLine($"加法启动次序{i}");
counterBase.Decrement();//减法
Console.WriteLine($"减法启动次序{i}"); }
}

看一下是否会出现资源抢夺

《C#多线程编程实战》1.10 lock关键字

其实还是会出现的。

但是为什么结果会是正确的呢?

我们再来改造一下:

 static void TestCounter(CounterWidthLock counterwidthlock)
{
for(int i=;i<;i++)
{
counterwidthlock.Increment();//加法
Console.WriteLine($"加法启动次序{i},当前的结果是{counterwidthlock.Count}");
counterwidthlock.Decrement();//减法
Console.WriteLine($"减法启动次序{i},当前的结果是{counterwidthlock.Count}"); }
}

《C#多线程编程实战》1.10 lock关键字

很明显,执行的顺序还是混乱。但是为什么结果却是正确呢?

我们来正式的讲讲lock关键字了

第一步 是lock建立互斥锁。

第二步执行lock内的方法执行完毕之后,

第三步释放lock互斥锁。

这一个过程中,只有一个线程能访问,如果有其他线程访问那就必须等待第一个线程执行完,并释放lock。

那么在代码中

 class CounterWidthLock : CounterBase
{
/// <summary>
/// 判断是否锁定资源
/// </summary>
public readonly object _syncRoot = new object(); public int Count { get; private set; } /// <summary>
/// Lock关键字减法
/// </summary>
public override void Decrement()
{
lock (_syncRoot)//lock关键字,锁定资源
{
Count--;
}
}
/// <summary>
/// lock关键字加法
/// </summary>
public override void Increment()
{
lock (_syncRoot)//lock关键字,锁定资源
{
Count++;
}
}
}

6:锁

15,25均为lock关键字。

那么什么是锁?

可以理解为一个唯一的资源,对象。这个对象最好不是公开的。 公开的话 会造成很多不便。

也就是T1 T2 T3 虽然都同时进行了TestCounter的方法。

但因为lock的存在,一个线程在执行加减的时候,其他线程是不可以干预的。也就是T1 执行加减,也许执行了三四次,T2 T3一直在等待。三个线程都是彬彬有礼等待其他来完成他们自己的事情,之后在是自己的。虽然谦让,但也有自己的原则,就是自己在做的时候,别人是不可以干预的

上一篇:C#中的lock关键字有何作用


下一篇:带你轻松了解C# Lock 关键字