lock 语句
为了线程同步
在一个地方使用lock语句,并不意味着 访问对象的其他线程都在等待
必须对 每个访问共享状态的线程 显式地 使用同步
lock(共享的 引用类型的 实例)
public class SharedState
{
public int State { get; set; }
}
class Program
{
static void Main()
{
int numTasks = 20;
var state = new SharedState();
var tasks = new Task[numTasks];
for (int i = 0; i < numTasks; i++)
{
tasks[i] = Task.Run(() => new Job(state).DoTheJob());
}
Task.WaitAll(tasks);
Console.WriteLine($"summarized {state.State}");
}
}
public class Job
{
private SharedState _sharedState;
public Job(SharedState sharedState)
{
_sharedState = sharedState;
}
public void DoTheJob()
{
for (int i = 0; i < 50000; i++)
{
lock (_sharedState)
{
_sharedState.State += 1;
}
}
}
}
输出:
summarized 1000000
对比一: 这里的另一种做法 依然 无法消除 争用条件。这另一种做法是:
仅把 共享的 引用类型 变成 线程安全的,如下:
public class SharedState
{
private int _state = 0;
private object _syncRoot = new object();
public int State
{
get { lock(_syncRoot) { return _state; }}
set { lock(_sycnRoot) { return _state = value; } }
}
}
对比二: 这里的另另一种做法 可以消除 争用条件。这另另一种做法是:
修改 共享的 引用类型的 设计,使其封装
_state
的递增,并且让_state
的递增 是原子的
public class SharedState
{
private int _state = 0;
private object _syncRoot = new object();
public int State => _state;
public int IncrementState()
{
lock (_syncRoot)
{
return ++_state;
}
}
}
lock(typeof(StaticClass))
要锁定静态成员
把锁放在
object
类型或静态成员上
lock(typeof(StaticClass))
{
}
lock(this)
将 类的成员 设置为 线程安全的
- 一次只能有一个线程 可以访问 相同实例 的 DoThis(),DoThat()方法
public class Demo
{
public void DoThis()
{
lock(this)
{
}
}
public void DoThat()
{
lock(this)
{
}
}
}
lock(_syncRoot)
当 类的实例 需要从外部 被同步访问,但又不适宜在 类自身中 进行这种控制 的时候,就可以用以下写法...
public class Demo
{
private object _syncRoot = new object();
public void DoThis()
{
lock(_syncRoot)
{
}
}
public void DoThat()
{
lock(_syncRoot)
{
}
}
}
对比 lock(this)
和lock(_syncRoot)
自我感觉:
lock(this)
- 对DoThis(),DoThat() 方法的调用 是同步的
lock(_syncRoot)
:- DoThis(),DoThat() 方法 是同步的
- 对DoThis(),DoThat() 方法的调用 不是同步的
实现一个类:让它可以 有时是同步的,有时是异步的
public class Demo
{
public virtual bool IsSynchronized => false;
public virtual void DoThis()
{
}
public virtual void DoThat()
{
}
public static Demo Synchronized(Demo demo)
{
if(!demo.IsSynchronized)
{
return new SynchronizedDemo(demo);
}
return demo;
}
private class SynchronizedDemo : Demo
{
private object _syncRoot = new object();
private Demo _demo;
public SynchronizedDemo(Demo demo)
{
_demo = demo;
}
public override bool IsSynchronized => true;
public override void DoThis()
{
lock(_syncRoot)
{
_demo.DoThis();
}
}
public override void DoThat()
{
lock(_syncRoot)
{
_demo.DoThat();
}
}
}
}
Monitor类
lock
语句 由C#编译为Monitor
类
Monitor.Enter()
: 一直等待,直到线程锁定对象为止Monitor.Exit()
:- 解除锁定
- 如果同步访问的区域有了异常,就会在
finally
里调用此方法解除锁定
lock(obj)
{
}
编译为
Monitor.Enter(obj);
try
{
//obj的同步访问区域
}
finally
{
Monitor.Exit(obj);
}
与
lock
相比,Monitor
类的主要优点是:可以添加一个等待锁定的超时值
Monitor.Enter(_obj, 500, ref _lockTaken)
- 超时值:500毫秒
- 是否锁定:
_lockTaken
- 如果锁定,则被置为
true
- 如果未锁定 且超时,则被置为
false
- 如果锁定,则被置为
bool _lockTaken = false;
Monitor.Enter(_obj, 500, ref _lockTaken);
if(_lockTaken)
{
try
{
//锁定
//obj的同步访问区域
}
finally
{
Monitor.Exit(obj);
}
}
else
{
//未锁定
//做其他事
}
SpinLock
以下情况可以使用SpinLock
:
如果 被锁定的对象 由于垃圾收集 系统开销过高
如果 有大量的锁定 且 锁定的时间总是非常短
使用SpinLock
需要注意:
避免使用多个
SpinLock
不要调用 任何可能阻塞 的内容
传递
SpinLock
实例的时候要小心。因为SpinLock
是结构,把一个变量赋予另一个变量会创建一个副本。要总是通过引用传递SpinLock
实例
SpinLock
的用法:
- 用
Enter()
或TryEnter()
获得锁定 - 用
Exit()
释放锁定 - 属性
IsHeld
: 检查当前是否锁定 - 属性
IsHeldByCurrentThread
:检查当前是否锁定