AND型信号量。大家可能都听说过,也可能有一定的了解,但是你用过吗?今天用Java来模拟实现!这篇文章是关于上一篇文章(进程同步机制)的一个实践,通过JUC提供的一些机制来模拟一些OS中的AND型信号量,因为record型信号量可以等同于in提供的Semaphore(信号量)JUC,但由于某些原因(主要是过时的),JUC不提供AND型信号量。今天就手动写一个AND型信号量对应的Swait操作和Ssignal操作(这里不懂的可以看之前的理论文章)。通过这篇博文,您可以对进程同步机制有更深入的了解。1.一个错误的例子这里先说明一下,为了满足线程申请信号量不成功,进程会被阻塞,插入到相应的队列中,所以使用了ReentrantLock+Condition来实现Swait方法。废话不多说,直接上代码://数据定义staticLocklock=newReentrantLock();staticConditioncondition1=lock.newCondition();staticConditioncondition2=lock.newCondition();publicstaticvoidSwait(Stringid,Semaphores1,Semaphores2)throwsInterruptedException{lock.tryLock(1,TimeUnit.SECONDS);log.info("当前两个信号量的状态:【{},{}】",s1.availablePermits(),s2.availablePermits());//availablePermits可以拿到信号量了if(s1.availablePermits()<1||s2.availablePermits()<1){if(s1.availablePermits()<1){log.info("线程[{}]被挂起到信号量中【{}】",",id,s1);//阻塞并插入condition1的阻塞队列condition1.await();}else{log.info("线程【{}】被挂起到信号数量【{}】",",id,s2);//阻塞,插入condition2的阻塞队列condition2.await();}log.info("挂起的线程【{}】被唤醒执行。",id);}else{log.info("为线程[{}]分配资源!",id);s1.acquire();s2.acquire();}lock.unlock();}publicstaticvoidSsignal(Semaphores1,Semaphores2)throwsInterruptedException{log.info("线程[{}]释放资源",id);lock.tryLock(1,TimeUnit.SECONDS);s1.release();s2.release();//唤醒等待对于队列中的线程condition.signal();lock.unlock();}大家仔细看上面的代码,这是我刚开始写的代码,乍一看好像没什么问题,但是有一个隐藏在里面的坑,在Swait方法中,调用condition1.await(),此时线程阻塞在这一行,但是当被另一个线程唤醒(调用Ssignal)时,会在下一个继续执行blocked行,但是在后面的代码中,并没有申请到信号量,而是直接swait成功了,这样执行Ssignal时,信号量会凭空增加,系统中的资源数量无法正确表达新的。2.一个简单的例子下面我们将优化代码。您可以查看AND类型的信号量。当由于资源不足时,需要将线程插入到第一个不能满足条件的信号(即Si<1)数量对应的等待队列中,并将程序计数器放在Swait操作的开始,所以我们修改Swait代码如下:publicstaticvoidSwait(Stringid,Semaphores1,Semaphores2)throwsInterruptedException{lock.tryLock(1,TimeUnit.SECONDS);log.info("当前两个信号量的状态:[{},{}]",s1.availablePermits(),s2.availablePermits());//如果应用不可用,挂起线程,将线程插入条件队列while(s1.availablePermits()<1||s2.availablePermits()<1){if(s1.availablePermits()<1){log.info("线程【{}】被挂起到信号量【{}】",id,s1);condition1.await();}else{log.info("线程【{}】被挂起到信号量【{}】",id,s2);condition2.await();}log.info("挂起的线程[{}]是唤醒执行。",id);}log.info("为威胁分配资源d[{}]!",id);s1.acquire();s2.acquire();lock.unlock();}在上面的代码中,我们将请求的资源放入循环条件中,以满足将程序计数器放入Swait操作的开始,每次唤醒后,都需要重新判断资源是否充足,如果充足则跳出循环,否则会再次阻塞自己。3.一个可以同时申请N个的Swait操作如果知道信号量的类型(系统中的资源类型)的数量,其实上面的代码已经可以满足一定的需求了,我们只需要全部写成信号量只需在参数列表中输入即可。但是为了代码复用,这里并不尽如人意,所以我们再次改进代码,代码如下:publicstaticvoidSwait(Stringid,Semaphore...list)throwsInterruptedException{lock.lock();//如果资源不足,挂起while(true){intcount=0;//循环判断参数列表中信号量的可用值for(Semaphoresemaphore:list){if(semaphore.availablePermits()>0){count++;}}//如果所有资源都满足,跳出循环分配资源if(count==list.length){break;}log.info("线程[{}]被挂起-----",id);//阻塞当前线程条件1.await();log.info("挂起的线程[{}]被唤醒执行。",id);}log.info("分配资源forthread【{}】!",id);//为(Semaphoresemaphore:list){semaphore.acquire();}lock.unlock();}publicstaticvoidSignal(Stringid,Semaphore...list)throwsInterruptedException{log分配资源.info("线程[{}]释放资源",id);lock.tryLock(1,TimeUnit.SECONDS);//循环释放信号量for(Semaphoresemaphore:list){semaphore.release();}//唤醒等待队列中的线程condition.signal();lock.unlock();}为此,我们将方法中的信号量列表改为可变参数列表,这样我们就可以传递参数了进行起来方便,但是会存在一些问题,比如是否不能约束“借”和“还”信号量的数量一致。并且由于信号量的数量是不确定的,所以不可能为每个信号量都创建一个新的条件变量(Condition)。因此,在上面的代码中,所有的信号量共享一个条件变量,所有被阻塞的线程都被插入到它们的阻塞队列中。4.一个完整的例子这里我们用一个经典的进程同步问题来演示我们用Java模拟的AND型信号量。这里,我们用生产者消费者问题来论证。完整代码如下://使用保证互斥访问临界区(缓冲区)staticfinalSemaphoremutex=newSemaphore(1);//buffer,最大容量为50staticList
