改善C#程序的建议8:避免锁定不恰当的同步对象
2012-08-07 11:42
281 查看
在C#中让线程同步的另一种编码方式就是使用线程锁。所谓线程锁,就是锁住一个资源,使得应用程序只能在此刻有一个线程访问该资源。可以用下面这句不是那么贴切的话来理解线程锁的作用:锁,就是让多线程变成单线程。在C#中,可以将被锁定的资源理解成new出来的普通对象。
既然需要锁定的资源就是一个C#中的对象,我们就该仔细思考,到底什么样的对象能够成为一个锁对象(也叫同步对象)?在选择同步对象的时候,应当始终注意以下几点:
q同步对象在需要同步的多个线程中是可见的、同一个对象;
q非静态方法中,静态变量不应作为同步对象;
q值类型对象不能作为同步对象;
q避免将字符串作为同步对象。
q降低同步对象的可见性。
第一点,需要锁定的对象在多个线程中是可见的、同一个对象
“可见的”这是显而易见的,如果对象不可见,就不能被锁定。“同一个对象”,这理解起来也很好理解,如果锁定的不是同一个对象,那又如何来同步两个对象呢?可是,不见得我们在这上面不会犯错误。为了阐述本建议,我们先模拟一个必须使用到锁的场景:在遍历一个集合的过程中,同时在另外一个线程中删除集合中的某项。下面的这个例子中,如果没有lock语句,将会抛出异常InvalidOperationException:“集合已修改;可能无法执行枚举”:
publicpartialclass FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
AutoResetEvent autoSet =new AutoResetEvent(false);
List<string> tempList =new List<string>() { "init0", "init1", "init2" };
privatevoid buttonStartThreads_Click(object sender, EventArgs e)
{
object syncObj =newobject();
Thread t1 =new Thread(() =>
{
//确保等待t2开始之后才运行下面的代码
autoSet.WaitOne();
lock (syncObj)
{
foreach (var item in tempList)
{
Thread.Sleep(1000);
}
}
});
t1.IsBackground =true;
t1.Start();
Thread t2 =new Thread(() =>
{
//通知t1可以执行代码
autoSet.Set();
//沉睡1秒是为了确保删除操作在t1的迭代过程中
Thread.Sleep(1000);
lock (syncObj)
{
tempList.RemoveAt(1);
}
});
t2.IsBackground =true;
t2.Start();
}
}
这是一个Winform窗体应用程序,我们需要演示的功能在按钮的点击事件中。对象syncObj对于线程t1和t2来说,在CLR中肯定是同一个对象。所以上面的示例运行没有问题。
现在,我们将以上示例重构一下。将实际的工作代码移到一个类型SampleClass中去,该示例要在多个SampleClass实例间操作一个静态字段:
privatevoid buttonStartThreads_Click(object sender, EventArgs e)
{
SampleClass sample1 =new SampleClass();
SampleClass sample2 =new SampleClass();
sample1.StartT1();
sample2.StartT2();
}
class SampleClass
{
publicstatic List<string> TempList
既然需要锁定的资源就是一个C#中的对象,我们就该仔细思考,到底什么样的对象能够成为一个锁对象(也叫同步对象)?在选择同步对象的时候,应当始终注意以下几点:
q同步对象在需要同步的多个线程中是可见的、同一个对象;
q非静态方法中,静态变量不应作为同步对象;
q值类型对象不能作为同步对象;
q避免将字符串作为同步对象。
q降低同步对象的可见性。
第一点,需要锁定的对象在多个线程中是可见的、同一个对象
“可见的”这是显而易见的,如果对象不可见,就不能被锁定。“同一个对象”,这理解起来也很好理解,如果锁定的不是同一个对象,那又如何来同步两个对象呢?可是,不见得我们在这上面不会犯错误。为了阐述本建议,我们先模拟一个必须使用到锁的场景:在遍历一个集合的过程中,同时在另外一个线程中删除集合中的某项。下面的这个例子中,如果没有lock语句,将会抛出异常InvalidOperationException:“集合已修改;可能无法执行枚举”:
publicpartialclass FormMain : Form
{
public FormMain()
{
InitializeComponent();
}
AutoResetEvent autoSet =new AutoResetEvent(false);
List<string> tempList =new List<string>() { "init0", "init1", "init2" };
privatevoid buttonStartThreads_Click(object sender, EventArgs e)
{
object syncObj =newobject();
Thread t1 =new Thread(() =>
{
//确保等待t2开始之后才运行下面的代码
autoSet.WaitOne();
lock (syncObj)
{
foreach (var item in tempList)
{
Thread.Sleep(1000);
}
}
});
t1.IsBackground =true;
t1.Start();
Thread t2 =new Thread(() =>
{
//通知t1可以执行代码
autoSet.Set();
//沉睡1秒是为了确保删除操作在t1的迭代过程中
Thread.Sleep(1000);
lock (syncObj)
{
tempList.RemoveAt(1);
}
});
t2.IsBackground =true;
t2.Start();
}
}
这是一个Winform窗体应用程序,我们需要演示的功能在按钮的点击事件中。对象syncObj对于线程t1和t2来说,在CLR中肯定是同一个对象。所以上面的示例运行没有问题。
现在,我们将以上示例重构一下。将实际的工作代码移到一个类型SampleClass中去,该示例要在多个SampleClass实例间操作一个静态字段:
privatevoid buttonStartThreads_Click(object sender, EventArgs e)
{
SampleClass sample1 =new SampleClass();
SampleClass sample2 =new SampleClass();
sample1.StartT1();
sample2.StartT2();
}
class SampleClass
{
publicstatic List<string> TempList
相关文章推荐
- 改善C#程序的建议8:避免锁定不恰当的同步对象
- 编写高质量代码:改善c#程序的157个建议之“避免锁定不恰当的同步对象”
- 改善C#程序的建议8:避免锁定不恰当的同步对象
- 改善C#程序的建议8:避免锁定不恰当的同步对象
- 编写高质量代码:改善c#程序的157个建议之“避免锁定不恰当的同步对象”
- 改善C#程序的建议8:避免锁定不恰当的同步对象
- 编写高质量代码改善C#程序的157个建议——建议73:避免锁定不恰当的同步对象
- 建议73:避免锁定不恰当的同步对象
- 编写高质量代码改善C#程序的157个建议——建议8: 避免给枚举类型的元素提供显式的值
- 编写高质量代码改善C#程序的157个建议——建议53:必要时应将不再使用的对象引用赋值为null
- 编写高质量代码改善C#程序的157个建议——建议145:避免过长的方法和过长的类
- 编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception]
- 编写高质量代码改善C#程序的157个建议——建议112:将现实世界中的对象抽象为类,将可复用对象圈起来就是命名空间
- 编写高质量代码改善C#程序的157个建议——建议95:避免在构造方法中调用虚成员
- 编写高质量代码改善C#程序的157个建议[动态数组、循环遍历、对象集合初始化]
- 编写高质量代码改善C#程序的157个建议[避免finaly内的无效代码、避免嵌套异常、避免吃掉异常、注意循环异常处理]
- [编写高质量代码:改善java程序的151个建议]建议43 避免对象浅拷贝; 建议44:推荐使用序列化实现对象的深拷贝
- 编写高质量代码改善C#程序的157个建议——建议125:避免用FCL的类型名称命名自己的类型
- 编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]
- 编写高质量代码改善C#程序的157个建议——建议63:避免“吃掉”异常