当前位置: 首页 > 科技观察

Java并发编程(JUC)模拟AND型信号量

时间:2023-03-12 12:31:29 科技观察

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,最大容量为50staticListbuffer=newArrayList<>();//也可以放入缓冲区的消息数量staticfinalSemaphoreempty=newSemaphore(50);//缓冲区中的消息数量staticfinalSemaphorefull=newSemaphore(0);//重入锁和条件变量staticLocklock=newReentrantLock();staticConditioncondition=lock.newCondition();//与辅助简单生成消息一起使用staticIntegercount=0;//ProducerstaticclassProducerextendsThread{Producer(Stringname){super.setName(name);}@Overridepublicvoidrun(){do{try{Swait(this.getName(),mutex,empty);log.info("Producedamessage:【{}】",count);buffer.add(count++);Thread.sleep(1000);Ssignal(this.getName(),mutex,full);}catch(InterruptedExceptione){log.error("产生消息时发生异常!");}}while(true);}}//ConsumerstaticclassConsumerextendsThread{Consumer(Stringname){super.setName(name);}@Overridepublicvoidrun(){do{try{Swait(this.getName(),mutex,full);log.info("消耗了一个Message:【{}】",buffer.remove(0));Thread.sleep(1000);Ssignal(this.getName(),mutex,empty);}catch(InterruptedExceptione){log.error("消费消息Anwhen!");}}while(true);}}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);condition.await();log.info("挂起的线程[{}]被唤醒并执行",id);}log.info("为线程[{}]分配资源!",id);for(Semaphoresemaphore:list){semaphore.acquire();}lock.unlock();}publicstaticvoidSsignal(Stringid,Semaphore...list)throwsInterruptedException{log.info("Thread[{}]releasedresources",id);lock.tryLock(1,TimeUnit.SECONDS);for(Semaphoresemaphore:list){semaphore.release();}//唤醒等待队列中的一个线程condition.signal();lock.unlock();}publicstaticvoidmain(String[]args){Producerp1=newProducer("p1");Consumerc1=newConsumer("c1");p1.start();c1.start();}上面的代码可以直接执行,如果不需要使用参数列表,可以替换上面的Swait方法(记得创建对应的条件变量).下图是部分执行结果:再次在分界线下方,本文到此结束。本文内容全部由博主自己整理,结合自己的理解和代码编写.如有错误请重新转批评指正。本文所有java代码均通过测试。如果大家对此有什么疑问,可以在评论区留言。欢迎您的留言和讨论。此外,原创并不容易。如果本文对您有帮助,请评论点赞,表示支持。希望本文能帮助大家理解和加深对进程同步的理解,也能帮助大家理解Java并发编程。

猜你喜欢