作者个人研发在高并发场景下提供了一个简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。开源半年多以来,已成功为十几家中小企业提供精准定时调度解决方案,经受住了生产环境的考验。为了造福更多的童鞋,这里给出开源框架的地址:https://github.com/sunshinelyz/mykit-delay前面写了很多次,当我们在并发编程中涉及到锁操作时,代码block加锁操作真的合理吗?有什么需要优化的地方吗?文章《【高并发】优化加锁方式时竟然死锁了!!》中解释了这个问题。我们介绍了死锁发生的四个必要条件。只有同时满足这四个条件,才会发生死锁。.其中,我们在阻塞请求和维护条件时,采用一次性申请所有资源的方式。比如在完成转账操作的过程中,我们一次性申请了A账户和B账户,两个账户都申请成功之后再执行转账操作。其中,在我们实现的transfer方法中,采用无限循环循环获取资源,直到同时获取到A账户和B账户。核心代码如下。//申请转出账户和转入账户直到成功while(!requester.applyResources(this,target)){//循环体为空;}如果ResourcesRequester类的applyResources()方法执行了一个时间很短,且程序并发引起的冲突小,程序循环数次到数十次后可同时获取转出账号和转入账号。这个方案是可行的。但是如果ResourcesRequester类的applyResources()方法执行时间比较长,或者程序并发冲突比较大,可能需要几千个周期才能获取到转出账号和转入账号同一时间。这样太消耗CPU资源了,这个时候这个方案是行不通的。那么,有没有办法优化这个解决方案呢?问题分析既然一直使用死循环获取资源的方案是有问题的,那我们换个思路想一想。当线程执行时,发现条件不满足,线程是否可以进入等待状态?当条件满足时,通知等待线程重新执行?即如果线程要求的条件不满足,我们就让线程进入waitingstate状态;如果满足线程要求的条件,我们会通知等待线程重新执行。这样就可以避免程序陷入循环等待,消耗CPU的问题。那么,问题又来了!不满足条件时如何让线程等待?如何在满足条件时唤醒线程?是的,这是一个问题!不过这个问题解决起来也很简单。简单的说,就是利用线程的等待和通知机制。线程等待和通知机制我们可以通过线程等待和通知机制来优化阻塞请求和维护条件时循环获取账户资源的问题。具体的等待和通知机制如下。执行线程首先获取互斥量。如果线程继续执行时不满足要求的条件,则释放互斥锁,进入等待状态;当满足线程继续执行的条件时,通知等待线程重新获取互斥量。那么,说了这么多,Java支持这种线程等待和通知机制吗?其实这个问题有点废话。Java这么优秀(great)的语言肯定是支持的,而且实现起来也比较简单。Java中线程等待和通知机制的实现实际上,使用Java语言实现线程等待和通知机制的方法有很多种。这里我简单列举一种方法。其他的方法你可以自己去想和实现。不懂的也可以问我哦!在Java语言中,一个简单的实现线程等待和通知机制的方法是使用synchronized并结合wait()、notify()和notifyAll()方法。实现原理当我们使用synchronized加锁时,只允许一个线程进入被synchronized保护的代码块,即临界区。如果一个线程进入了临界区,其他线程就会进入阻塞队列等待。这个阻塞队列和synchronizedmutex是一对一的关系,即一个mutex对应一个独立的阻塞队列。.在并发编程中,如果一个线程获取了synchronizedmutex但不满足继续向下执行的条件,则需要进入等待状态。此时,可以使用Java中的wait()方法来实现。当wait()方法被调用时,当前线程会被阻塞,会进入一个等待队列等待。这个因为调用wait()方法进入的等待队列,也是mutex的等待队列。而且,当线程进入等待队列后,会释放自己获取的互斥锁,让其他线程有机会获取互斥锁,进入临界区。整个过程可以用下图表示。当满足线程执行的条件时,可以使用Java提供的notify()和notifyAll()方法通知互斥等待队列中的线程。我们可以用下图来简单的表示这个过程。这里,需要注意以下几点:(1)当使用notify()和notifyAll()方法通知线程时,调用notify()和notifyAll()方法时,线程的执行情况都满足了,但是当线程真正执行的时候,条件可能已经不满足了,可能还有其他线程进入了临界区执行。(2)当被通知的线程继续执行时,需要先获取互斥量,因为在调用wait()方法等待时互斥量已经释放。(3)wait()、notify()、notifyAll()方法操作的队列就是互斥量的等待队列。如果同步锁是this对象,则必须使用this.wait()、this.notify()和this.notifyAll()方法;如果同步锁是目标对象,一定要使用target.wait()、target.notify()和target.notifyAll()方法。(4)wait()、notify()、notifyAll()方法调用的前提是已经获取到对应的mutex,也就是说wait()、notify()、notifyAll()方法都在同步方法或在代码块中调用。如果这三个方法在synchronized方法之外或者代码块之外调用,或者锁定的对象是this,使用目标对象调用这三个方法,JVM会抛出java.lang.IllegalMonitorStateException。在实现实现逻辑之前,我们需要考虑以下问题:选择哪个互斥量在前面的程序中,我们在TansferAccount类中有一个ResourcesRequester类的单例对象,所以我们可以将其作为互斥量。每个人都需要明白这一点。线程执行转账操作条件前,转出账户和转入账户均未分配。线程什么时候进入等待状态?当不满足线程继续执行的条件时,进入等待状态。何时通知等待线程执行当有线程释放账户资源时,通知等待线程继续执行。综上所述,我们可以得到如下核心代码。while(conditionnotsatisfied){wait();}那么,问题来了!为什么在while循环中调用了wait()方法?因为当wait()方法返回的时候,有可能线程执行的条件发生了变化,也就是之前条件满足了,现在不满足了,所以需要重新检查是否条件满足。实现代码我们优化后的ResourcesRequester类的代码如下所示。publicclassResourcesRequester{//存储应用资源的集合privateList
