深入浅出多线程系列之四:简单的同步 lock

简介:

1: 考虑下下面的代码:

复制代码
class  ThreadUnsafe
    {
        
static   int  _val1  =   1 , _val2  =   1 ;

        
internal   static   void  Go()
        {
            
if  (_val2  !=   0 )
            {
                Console.WriteLine(_val1 
/ _val2);
            }
            _val2 
=   0 ;
        }
    }
复制代码

 

这段代码是非线程安全的,假设有两个线程A,BA,B都执行到了Go方法的if判断中,假设_val2=1.所以两个线程A,B都通过if判断,

A执行了Console.WriteLine方法,然后退出if语句,执行_val2=0,此时_val2=0.

但是此时线程B才刚刚执行到Console.WriteLine方法,而此时_val2=0.所以你有可能会得到一个divide by zero 的异常。

 

为了保证线程安全,我们可以使用Lock关键字,例如:

复制代码
        static   readonly   object  _locker  =   new   object ();
        
static   int  _val1  =   1 , _val2  =   1 ;

        
internal   static   void  Go()
        {
            
lock  (_locker)
            {
                
if  (_val2  !=   0 )
                {
                    Console.WriteLine(_val1 
/  _val2);
                }
                _val2 
=   0 ;
            }
        }
复制代码

此时线程AB都只能有一个可以获得_locker锁,所以只能有一个线程来执行lock块的代码。

C#Lock关键字实际上是Monitor.Enter,Monitor.Exit的缩写。例如上面的代码和下面的等价。

复制代码
            Monitor.Enter(_locker);
            
try
            {
                
if  (_val2  !=   0 )
                {
                    Console.WriteLine(_val1 
/  _val2);
                }
                _val2 
=   0 ;
            }
            
finally  { Monitor.Exit(_locker); }
复制代码

如果在调用Monitor.Exit之前没有调用Monitor.Enter,则会抛出一个异常。

 

不知道大家注意到没有,Monitor.Enter  Try 方法之间可能会抛出异常。

例如在线程上调用Abort,或者是OutOfMemoryException

为了解决这个问题CLR 4.0提供了Monitor.Enter的重载,增加了lockTaken 字段,当Monitor.Enter成功获取锁之后,lockTaken就是True,否则为False

我们可以将上面的代码改成下面的版本。

复制代码
            bool  lockTaken  =   false ;
            
try
            {
                Monitor.Enter(_locker, 
ref  lockTaken);
                
//  Do something..
            }
            
finally
                
if (lockTaken) {
                    Monitor.Exit(_locker);
                }
            }
复制代码

Monitor也提供了TryEnter方法,并且可以传递一个超时时间。如果方法返回True,则代表获取了锁,否则为false

 

2:选择同步对象。

Monitor.Enter方法的参数是一个object类型,所以任何对象都可以是同步对象,考虑下下面的代码:

  1. int i=5; lock(i){}         // 锁定值类型
  2. lock(this){}           // 锁定 this 对象
  3. lock(typeof(Product)){}      // 锁定 type 对象。
  4. string str="dddd"; lock(str){}   // 锁定字符串

1:锁定值类型会将值类型进行装箱,所以Monitor.Enter进入的是一个对象,但是Monitor.Exit()退出的是另一个不同的对象。

23:锁定thistype对象,会导致无法控制锁的逻辑,并且它很难保证不死锁和频繁的阻塞,在相同进程中锁定type对象会穿越应用程序域。

4:由于字符串驻留机制,所以也不要锁定string,关于这点,请大家去逛一逛老A的博客。

 

3:嵌套锁:

同一个线程可以多次锁定同一对象。例如

复制代码
lock (locker)
    
lock (locker)
        
lock (locker)
        {
            
//  do something
     }
复制代码

或者是:

Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker);
// Do something.
Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);

 

当一个线程使用一个锁调用另一方法的时候,嵌套锁就非常的有用。例如:

复制代码
       static   readonly   object  _locker  =   new   object ();

        
static   void  Main()
        {
            
lock  (_locker)
            { 
                AnotherMethod();
            }
        }

        
static   void  AnotherMethod()
        {
            
lock  (_locker){  // dosomething;}
        }
复制代码

 

4:死锁:

先看下面的代码:

复制代码
        static   object  locker1  =   new   object ();
        
static   object  locker2  =   new   object ();

        
public   static   void  MainThread()
        {
            
new  Thread(()  =>
                {
                    
lock  (locker1)   //获取锁locker1
                    {
                        Thread.Sleep(
1000 );
                        
lock  (locker2) //尝试获取locker2
                        {
                            Console.WriteLine(
" locker1,locker2 " );
                        }
                    }
                }).Start();
            
lock  (locker2) //获取锁locker2
            {
                Thread.Sleep(
1000 );
                
lock  (locker1) //尝试获取locker1
                {
                    Console.WriteLine(
" locker2,locker1 " );
                }
            }
        }
复制代码

在这里

主线程先获取locker2的锁,然后sleep,接着尝试获取locker1的锁。

副线程先获取locker1的锁,然后sleep,接着尝试获取locker2的锁。

程序进入了死锁状态,两个线程都在等待对方释放自己等待的锁。 

CLR作为一个独立宿主环境,它不像SQL Server一样,它没有自动检测死锁机制,也不会结束一个线程来破坏死锁。死锁的线程会导致部分线程无限的等待。

 

下篇文章会介绍一些其他同步构造。






本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/05/23/2053604.html,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
编解码 数据安全/隐私保护 计算机视觉
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
如何使用OpenCV进行同步和异步操作来打开海康摄像头,并提供了相关的代码示例。
154 1
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
|
2月前
|
Java 调度
Java 线程同步的四种方式,最全详解,建议收藏!
本文详细解析了Java线程同步的四种方式:synchronized关键字、ReentrantLock、原子变量和ThreadLocal,通过实例代码和对比分析,帮助你深入理解线程同步机制。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 线程同步的四种方式,最全详解,建议收藏!
|
2月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
54 4
|
3月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
71 1
|
3月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
37 2
|
4月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
53 7
|
3月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
3月前
多线程通信和同步的方式有哪些?
【10月更文挑战第6天】
176 0
|
5月前
|
Java
在Java多线程领域,精通Lock接口是成为高手的关键。
在Java多线程领域,精通Lock接口是成为高手的关键。相较于传统的`synchronized`,Lock接口自Java 5.0起提供了更灵活的线程同步机制,包括可中断等待、超时等待及公平锁选择等高级功能。本文通过实战演练介绍Lock接口的核心实现——ReentrantLock,并演示如何使用Condition进行精确线程控制,帮助你掌握这一武林秘籍,成为Java多线程领域的盟主。示例代码展示了ReentrantLock的基本用法及Condition在生产者-消费者模式中的应用,助你提升程序效率和稳定性。
59 2
|
5月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
34 2