(点击上方蓝字,可快速 Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形: ...... QuuoQuu=nwQuu(); ...... Monitor.Entr(oQuu); ......//现在oQuu对象只能被当前线程操纵了 Monitor.Exit(oQuu);//释放锁 如上所示,当一个线程调用Monitor.Entr()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch-finally结构中的finally代码块里。 对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息: 其一是现在持有锁的线程的引用; 其二是一个预备队列,队列中保存了已经准备好获取锁的线程; 其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。 当拥有对象锁的线程准备释放锁时,它使用Monitor.Puls()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。 下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。 这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示(注释中介绍了该程序的精要所在)。 用到的系统命名空间如下: usingSystm; usingSystm.Thrading; 首先,定义一个被操作的对象的类Cll,在这个类里,有两个方法:RadFromCll()和WritToCll。消费者线程将调用RadFromCll()读取cllContnts的内容并且显示出来,生产者进程将调用WritToCll()方法向cllContnts写入数据。 示例如下: publicclassCll { intcllContnts;//Cll对象里边的内容 boolradrFlag=fals;//状态标志,为tru时可以读取,为fals则正在写入 publicintRadFromCll() { lock(this)//Lock关键字保证了什么,请大家看前面对lock的介绍 { if(!radrFlag)//如果现在不可读取 { try { //等待WritToCll方法中调用Monitor.Puls()方法 Monitor.Wait(this); } catch(SynchronizationLockExcption) { Consol.WritLin(); } catch(ThradIntrruptdExcption) { Consol.WritLin(); } } Consol.WritLin(Consum:{0},cllContnts); radrFlag=fals; //重置radrFlag标志,表示消费行为已经完成 Monitor.Puls(this); //通知WritToCll()方法(该方法在另外一个线程中执行,等待中) } rturncllContnts; } publicvoidWritToCll(intn) { lock(this) { if(radrFlag) { try { Monitor.Wait(this); } catch(SynchronizationLockExcption) { //当同步方法(指Monitor类除Entr之外的方法)在非同步的代码区被调用 Consol.WritLin(); } catch(ThradIntrruptdExcption) { //当线程在等待状态的时候中止 Consol.WritLin(); } } cllContnts=n; Consol.WritLin(Produc:{0},cllContnts); radrFlag=tru; Monitor.Puls(this); //通知另外一个线程中正在等待的RadFromCll()方法 } } } 下面定义生产者类CllProd和消费者类CllCons,它们都只有一个方法ThradRun(),以便在Main()函数中提供给线程的ThradStart代理对象,作为线程的入口。 publicclassCllProd { Cllcll;//被操作的Cll对象 intquantity=1;//生产者生产次数,初始化为1 publicCllProd(Cllbox,intrqust) { //构造函数 cll=box; quantity=rqust; } publicvoidThradRun() { for(intloopr=1;loopr=quantity;loopr++) cll.WritToCll(loopr);//生产者向操作对象写入信息 } } publicclassCllCons { Cllcll; intquantity=1; publicCllCons(Cllbox,intrqust) { //构造函数 cll=box; quantity=rqust; } publicvoidThradRun() { intvalRturnd; for(intloopr=1;loopr=quantity;loopr++) valRturnd=cll.RadFromCll();//消费者从操作对象中读取信息 } } 然后在下面这个类MonitorSampl的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CllProd.ThradRun()方法和CllCons.ThradRun()方法对同一个Cll对象进行操作。 publicclassMonitorSampl { publicstaticvoidMain(String[]args) { intrsult=0;//一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生 Cllcll=nwCll(); //下面使用cll初始化CllProd和CllCons两个类,生产和消费次数均为20次 CllProdprod=nwCllProd(cll,20); CllConscons=nwCllCons(cll,20); Thradproducr=nwThrad(nwThradStart(prod.ThradRun)); Thradconsumr=nwThrad(nwThradStart(cons.ThradRun)); //生产者线程和消费者线程都已经被创建,但是没有开始执行 try { producr.Start(); consumr.Start(); producr.Join(); consumr.Join(); Consol.RadLin(); } catch(ThradStatExcption) { //当线程因为所处状态的原因而不能执行被请求的操作 Consol.WritLin(); rsult=1; } catch(ThradIntrruptdExcption) { //当线程在等待状态的时候中止 Consol.WritLin(); rsult=1; } //尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果 Environmnt.ExitCod=rsult; } } 在上面的例程中,同步是通过等待Monitor.Puls()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Puls)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Puls()发出的“脉冲”。 它的执行结果很简单: Produc:1 Consum:1 Produc:2 Consum:2 Produc:3 Consum:3 ... ... Produc:20 Consum:20 事实上,这个简单的例子已经帮助我们解决了多线程应用程序中可能出现的大问题,只要领悟了解决线程间冲突的基本方法,很容易把它应用到比较复杂的程序中去。 白癜风应注意些什么最好白癜风医院电话
|