深入浅出多线程系列之五:一些同步构造(上篇)

简介:

1:Mutex

Mutex 就像一个C# lock一样,不同的是它可以跨进程.

进入和释放一个Mutex要花费几毫秒,大约比C#lock50倍。

使用一个Mutex的实例,调用WaitOne方法来获取锁,ReleaseMutex方法来释放锁。

因为Mutex是跨进程的,所以我们可以使用Mutex来检测程序是否已经运行。

复制代码
        public   static   void  MainThread()
        {
            
using  (var mutex  =   new  Mutex( false " LoveJenny OneAtATimeDemo " ))
            {
                
if  ( ! mutex.WaitOne(TimeSpan.FromSeconds( 3 ),  false ))
                {
                    Console.WriteLine(
" 只能运行一个应用程序! " );
                    
return ;
                }

                RunProgram();
            }
        }
复制代码

 

2:Semaphore:

一个Semaphore就像一个酒吧一样,通过门卫来限制它的客人,一旦到达限制,没有人可以进入,

人们会在门外乖乖的排队,一旦有一个人离开酒吧,排队中的人就可以进入了一个了。

下面是个例子:

复制代码
    class  TheClub
    {
       
// 只能容纳三个人的酒吧
         static  SemaphoreSlim _sem  =   new  SemaphoreSlim( 3 );

        
public   static   void  MainThread()
        {
            
for  ( int  i  =   1 ; i  <=   5 ; i ++ )
                
new  Thread(Enter).Start(i);  // 有5个人向进入
        }
        
static   void  Enter( object  id)
        {
            Console.WriteLine(id 
+   "  想要进入了 " );
            _sem.Wait();
            Console.WriteLine(id
+ "  已经进入了! " );
            Thread.Sleep(
1000   *  ( int )id);
            Console.WriteLine(id 
+   "  离开了? " );
            _sem.Release();
        }
    }
复制代码

 

3:AutoResetEvent

一个AutoResetEvent就像十字转门一样,插入一张票就让一个人通过,”Auto”代表门会自动的关上。

在十字门外面的人可以调用WaitOne方法来阻塞,等待。一旦有人插入了票(调用Set方法),就可以让外面等待的人(调用WaitOne方法的线程)通过了。

创建AutoResetEvent有一个参数。

static EventWaitHandle _waitHandle = new AutoResetEvent(false);

其中falsemsdn的解释是:初始状态为非终止,

按照我个人的理解false代表了十字转门非终止,所以可以正常的进入,等待。

而如果是true的话:初始状态为终止,也就是代表已经调用了Set了,

就是说十字转门已经停止了,所以接下来如果有人调用了WaitOne方法,这个调用WaitOne方法的人直接就可以进入了,不需要再插入票(不需要调用Set)了,之后的调用和false一致,这一点可以认为AutoResetEvent具有记忆功能,它记住了上次门是打开的状态。所以调用waitone方法可以进入。

复制代码
class  ThreadAutoResetEvent
    {
        
static  EventWaitHandle _waitHandle  =   new  AutoResetEvent( false );

        
public   static   void  MainThread()
        {
            
new  Thread(Waiter).Start();
            Thread.Sleep(
2000 );
            _waitHandle.Set();
        }

        
static   void  Waiter()
        {
            Console.WriteLine(
" Waiting... " );
            _waitHandle.WaitOne();
            Console.WriteLine(
" Notified " );
        }
}
复制代码

很简单,Waiter执行到Waiting…后,就开始调用WaitOne了,所以在门外排队等待。

而主线程在睡了两秒后,开始插入一张票(Set).所以Waiter就继续执行,所以打印Notified

 

 

接下来我们使用AutoResetEvent来模拟实现生产消费问题:

 

复制代码
class  ProducerConsumerQueue:IDisposable
    {
        EventWaitHandle _wh 
=   new  AutoResetEvent( false );
        Thread _worker;
        
readonly   object  _locker  =   new   object ();
        Queue
< string >  _tasks  =   new  Queue < string > ();

        
public  ProducerConsumerQueue()
        {
            
// 创建并启动工作线程
            _worker  =   new  Thread(Work);
            _worker.Start();
        }

        
public   void  EnqueueTask( string  task)
        {
            
lock  (_locker) _tasks.Enqueue(task);
            _wh.Set(); 
// 一旦有任务了,唤醒等待的线程
        }

        
public   void  Dispose()
        {
            EnqueueTask(
null );
            _worker.Join(); 
// 等待_worker线程执行结束
            _wh.Close();
        }

        
void  Work()
        {
            
while  ( true )
            {
                
string  task  =   null ;
                
lock  (_locker)
                {
                    
if  (_tasks.Count  >   0
                    {
                        task 
=  _tasks.Dequeue();
                        
if  (task  ==   null )
                            
return ;
                    }
                    
if  (task  !=   null // 如果有任务的话,执行任务
                    {
                        Console.WriteLine(
" Performing task:  "   +  task);
                        Thread.Sleep(
1000 );
                    }
                    
else   // 否则阻塞,去睡觉吧
                    {
                        _wh.WaitOne();
                    }
                }
            }
        }
    }
复制代码

主线程调用如下:

复制代码
        public   static   void  Main()
        {
            
using  (ProducerConsumerQueue q  =   new  ProducerConsumerQueue())
            {
                q.EnqueueTask(
" Hello " );
                
for  ( int  i  =   0 ; i  <   10 ; i ++ ) q.EnqueueTask( " Say  "   +  i);
                q.EnqueueTask(
" Goodbye! " );
            }
        }
复制代码

 

4:ManualResetEvent

一个ManualResetEvent就是一个普通门,

调用Set方法门就打开了,允许任意数量的人进入。

调用WaitOne方法就开始等待进入。

调用Reset方法门就关闭了。

在一个关闭的门上调用WaitOne方法就会被阻塞。

当门下次被打开的时候,所有等待的线程都可以进入了。

除了这些不同外,一个ManualResetEventAutoResetEvent类似。

Framework4.0ManualResetEvent提供了一个优化版本。ManualResetEventSlim。后面的版本速度更快,并且支持取消(CancellationToken).

 







本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/05/24/2053677.html,如需转载请自行联系原作者
目录
相关文章
|
18天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
52 5
|
3月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
【6月更文挑战第20天】在Java多线程编程中,`synchronized`和`Lock`是两种关键的同步机制。`synchronized`作为内置关键字提供基础同步,简单但可能不够灵活;而`Lock`接口自Java 5引入,提供更复杂的控制和优化性能的选项。在低竞争场景下,`synchronized`性能可能更好,但在高并发或需要精细控制时,`Lock`(如`ReentrantLock`)更具优势。选择哪种取决于具体需求和场景,理解两者机制至关重要。
35 1
|
3月前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
【6月更文挑战第20天】Java多线程同步始于`synchronized`关键字,保证单线程访问共享资源,但为应对复杂场景,`Lock`接口(如`ReentrantLock`)提供了更细粒度控制,包括可重入、公平性及中断等待。通过实战比较两者在高并发下的性能,了解其应用场景。不断学习如`Semaphore`等工具并实践,能提升多线程编程能力。从同步起点到专家之路,每次实战都是进步的阶梯。
36 0
|
3月前
|
Java 程序员
从0到1,手把手教你玩转Java多线程同步!
【6月更文挑战第20天】从0到1学Java多线程同步:理解线程同步关键,掌握`synchronized`用法,探索`Lock`接口,实战演练并进阶学习锁升级、`Condition`及死锁预防,成为多线程大师!
19 0
|
18天前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
77 5
|
18天前
|
Java 程序员
从0到1,手把手教你玩转Java多线程同步!
从0到1,手把手教你玩转Java多线程同步!
16 3
|
18天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
78 1
|
23天前
|
存储 Java 开发者
HashMap线程安全问题大揭秘:ConcurrentHashMap、自定义同步,一文让你彻底解锁!
【8月更文挑战第24天】HashMap是Java集合框架中不可或缺的一部分,以其高效的键值对存储和快速访问能力广受开发者欢迎。本文深入探讨了HashMap在JDK 1.8后的底层结构——数组+链表+红黑树混合模式,这种设计既利用了数组的快速定位优势,又通过链表和红黑树有效解决了哈希冲突问题。数组作为基石,每个元素包含一个Node节点,通过next指针形成链表;当链表长度过长时,采用红黑树进行优化,显著提升性能。此外,还介绍了HashMap的扩容机制,确保即使在数据量增大时也能保持高效运作。通过示例代码展示如何使用HashMap进行基本操作,帮助理解其实现原理及应用场景。
23 1
|
1月前
|
Java 调度 开发者
Java并发编程:解锁多线程同步的奥秘
在Java的世界里,并发编程是提升应用性能的关键所在。本文将深入浅出地探讨Java中的并发工具和同步机制,带领读者从基础到进阶,逐步掌握多线程编程的核心技巧。通过实例演示,我们将一起探索如何在多线程环境下保持数据的一致性,以及如何有效利用线程池来管理资源。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你对Java并发编程有更深入的理解和应用。
|
22天前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
27 0