C# 多线程三:临界区 Monitor的理解与运用

目录

一.Monitor特点

二.Monitor和Lock的关系

1.关系

2.示例

例1.使用Lock

例2.使用Monitor.Entor(obj,ref lockTaken)

三.方法

常用方法

其他方法:

四.使用Monitor实现阻塞队列BlackQueue


一.Monitor特点

它使用独占锁的方式控制线程同步一个线程只有得到这把锁才可以对该对象进行操作,对象锁机制保证了同一时刻只有一个线程可以访问这个对象

注:一定是锁定引用类型 值类型被锁会有装箱操作 下次再次锁这个值又将装箱成一个新的对象 导致报错

二.Monitor和Lock的关系

1.关系

其实Lock在IL代码中会被翻译成Monitor。也就是Monitor.Enter(obj)和Monitor.Exit(obj).

Lock(obj)
{
 ....
}

等同于

try
{
        Monitor.Entor(obj,ref lockTaken)
        ....
}catch()
{

}finally
{
   if(lockTaken)
        Monitor.Exit(obj);
}

2.示例

例1.使用Lock

using System.Collections.Generic;
using System.Threading;


public class LudwigLock<T> {
    public List<T>  obj;
    int maxLength;
    public LudwigLock(int _max) {
        maxLength = _max;
        obj = new List<T>();
    }
    public void AddCharByObj(T a) {
        lock(obj) {
            while(obj.Count == maxLength) {
                Monitor.Wait(obj);
            }
            obj.Add(a);
            if(obj.Count == 1)
                Monitor.PulseAll(obj);
        }
    }
    public void SubCharByObj() {
        lock(obj) {
            while(obj.Count == 0) {
                Monitor.Wait(obj);
            }
            obj.RemoveAt(0);

            if(obj.Count == maxLength - 1)
                Monitor.PulseAll(obj);
        }
    }

    public int Count() {
        lock(obj) {
            return obj.Count;
        }
    }
    public string GetStr() {
        lock(obj) {
            string str = "";
            for(int i = 0; i < obj.Count; i++) {
                str += obj[i] + " - ";
            }
            return str;
        }
    }
}
static void Main(string[] args) {

        LudwigLock<string> ludwigLock = new LudwigLock<string>(5);
        Thread thread = new Thread(() => {
            for(int i = 0; i < 10; i++)
                ludwigLock.AddCharByObj(i.ToString());
        });

        Thread thread2 = new Thread(() => {
            for(int i = 0; i < 10; i++) {
                ludwigLock.SubCharByObj();
                Console.WriteLine(ludwigLock.GetStr());
            }
        });
        thread.Start();
        thread2.Start();
        Console.Read();
}

打印:

例2.使用Monitor.Entor(obj,ref lockTaken)

public class LudwigMonitor<T> {
    public List<T> ts = new List<T>();
    private int max;
    public LudwigMonitor(int _max) {
        max = _max;
    }
    public void Add(T item) {
        try {
             Monitor.Enter(ts, ref flag);
            while(ts.Count == max) {
                Monitor.Wait(ts);
            }
            ts.Add(item);
            if(ts.Count == 1)
                Monitor.PulseAll(ts);

        } catch(Exception ex) {
            Console.WriteLine(ex.ToString());
        } finally {
	if(flag)
            Monitor.Exit(ts);
        }
    }
    public void Sub() {
        try {
            Monitor.Enter(ts);
            while(ts.Count == 0) {
                Monitor.Wait(ts);
            }
            ts.RemoveAt(0);

            if(ts.Count == max - 1)
                Monitor.PulseAll(ts);

        } catch(Exception ex) {
            Console.WriteLine(ex);
        } finally {
            Monitor.Exit(ts);
        }
    }
    public string GetString() {
        string str = "";
        try {
            Monitor.Enter(ts);
            for(int i = 0; i < ts.Count; i++) {
                str += ts[i] + " - ";
            }
        } finally {
            Monitor.Exit(ts);
        }
        return str;
    }
}
static void Main(string[] args) {

        LudwigMonitor<string> ludwigMonitor = new LudwigMonitor<string>(5);
        Thread thread = new Thread(() => {
            for(int i = 0; i < 10; i++)
                ludwigMonitor.Add(i.ToString());
        });

        Thread thread2 = new Thread(() => {
            for(int i = 0; i < 10; i++) {
                ludwigMonitor.Sub();
                Console.WriteLine(ludwigMonitor.GetString());
            }
        });
        thread.Start();
        thread2.Start();
        Console.Read();
}

打印:

 由上面可以看出俩个例子效果一样

三.方法

常用方法

以下几个常用方法大多在例子1和例子2中已有使用,不再赘述

Enter(Object)

在指定对象上获取排他锁。

Exit(Object)

释放指定对象上的排他锁。

Wait(Object)

释放对象上的锁并阻止当前线程,直到它重新获取该锁。

Pulse(Object)

通知等待队列中的线程锁定对象状态的更改。

PulseAll(Object)

通知所有的等待线程对象状态的更改。

Wait(Object, Int32)

释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。

和Wait(Object)类似 添加了一个超时间隔 超过时间自动加入就绪队列


我们改一下例子2的Add方法
代码:

public void Add(T item) {
        try {
            Monitor.Enter(ts);
            while(ts.Count == max) {
                Monitor.Wait(ts, 1000);
                if(ts.Count == max)
                    max ++;
            }
            ts.Add(item);

            if(ts.Count == 1)
                Monitor.PulseAll(ts);

        } catch(Exception ex) {
            Console.WriteLine(ex.ToString());
        } finally {
            Monitor.Exit(ts);
        }
    }

设置超时之后max自增
打印:


Enter(Object, Boolean)

获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。
这个bool值的作用是防止未调用Monitor.Enter就调用Monitor.Exit会引发异常


一般用法:

try {
            Monitor.Enter(ts, ref flag);
            

        } catch(Exception ex) {
            Console.WriteLine(ex.ToString());
        } finally {
            if(flag) {
                Monitor.Exit(ts);
            }
        }

其他方法:

IsEntered(Object)
确定当前线程是否保留指定对象上的锁。

TryEnter(Object)
尝试获取指定对象的排他锁。

TryEnter(Object, Boolean)
尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。

TryEnter(Object, Int32)
在指定的毫秒数内尝试获取指定对象上的排他锁。

TryEnter(Object, Int32, Boolean)
在指定的毫秒数内尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获取了该锁。

TryEnter(Object, TimeSpan)
在指定的时间内尝试获取指定对象上的排他锁。

TryEnter(Object, TimeSpan, Boolean)
在指定的一段时间内尝试获取指定对象上的排他锁,并自动设置一个值,指示是否获得了该锁。

Wait(Object, Int32, Boolean)
释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 此方法还指定是否在等待之前退出上下文的同步域(如果在同步上下文中)然后重新获取该同步域。

Wait(Object, TimeSpan)
释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。

Wait(Object, TimeSpan, Boolean)
释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 可以在等待之前退出同步上下文的同步域,随后重新获取该域。

四.使用Monitor实现阻塞队列BlackQueue

代码:

using System.Collections.Generic;
using System.Threading;


public class BlackQueue<T> {
    private Queue<T> queue;
    int maxLen;
    bool isClose;
    public BlackQueue(int max) {
        queue = new Queue<T>();
        maxLen = max;
    }
    public void Enqueue(T item) {
        lock(queue) {
            while(queue.Count >= maxLen) {
                Monitor.Wait(queue);
            }
            queue.Enqueue(item);
            if(queue.Count == 1)
                Monitor.PulseAll(queue);
        }
    }
    public int Count() {
        lock(queue) {
            return queue.Count;
        }
    }
    public bool TryDequeue(out T item) {
        lock(queue) {
            while(queue.Count <= 0) {
                if(isClose) {
                    item = default(T);
                    return false;
                }
                Monitor.Wait(queue);
            }
            item = queue.Dequeue();
            if(queue.Count == maxLen - 1)
                Monitor.PulseAll(queue);
            return true;
        }
    }
    public void Close() {
        lock(queue) {
            isClose = true;
            Monitor.PulseAll(queue);
        }
    }
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一梭键盘任平生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值