7-2小记

第六讲 - 用"等待-通知"机制优化循环等待

引出问题:

       上一篇中,破坏死锁条件之一:占用且等待条件,采用的方式是一次性申请所有资源,并且死循环等待资源;

       代码如下:如果申请资源耗时长,并发量大,要循环上万次才能获取锁,非常消耗CPU;

       7-2小记

 

解决方案:

       使用线程阻塞的方式避免循环等待消耗CPU问题;当线程要求的条件不满足时,线程应该阻塞自己, 进入

       等待状态,当线程要求的条件满足后,通知等待的线程重新执行;

 

"等待-通知"机制:

        方法一:java语言内置synchronized配合wait()、notify()、notifyAll();

        0)等待队列与互斥锁是一对一关系,每个互斥锁都有自己的等待队列;

        1)同一时刻,只允许一个线程进入synchronized保护的临界区,当有一个线程进入临界区后,其他线程就

             只能在左边的等待队列里等待;

       2)当一个线程进入临界区后,由于某些条件不满足,需要进入等待状态,java对象的wait()方法就能满足这

            种需求,调用wait()方法后,当前线程被阻塞,并且进入右边的等待队列,右边的等待队列也是互斥锁的

            等待队列;

       3)线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获取锁,进入临

            界区;

       5)当满足条件时,调用java对象的notify()、notifyAll()方法通知等待队列中的线程,告诉它曾经满足过;

       6)notify()、notifyAll()只能保证在通知的时间点条件是满足的,而被通知线程的执行时间点和通知时间点基

            本上不会重合,所以当线程执行的时候,很可能条件已经不满足了;

       7)被通知的线程想要重新执行,仍然需要获取到互斥锁,因为曾经获取的锁在调用wait()时已经释放了;   

       8)wait()、notify()、notifyAll()方法操作的等待队列是互斥锁的等待队列;

            如果synchronized锁定的是this,那么对应的一定是this.wait()、this.notify()、this.notifyAll();

            如果synchronized锁定的是target,那么对应的一定是target.wait()、target.notify()、target.notifyAll();

       9)wait()、notify()、notifyAll能被调用的前提是已经获得了相应的互斥锁,所以在synchronized内部被调用;    

       7-2小记

 

案例分享:

      解决一次性申请转出账户和转入账户问题:

      1)互斥锁:因为Allocator是单例的,可以用this作为互斥锁;

      2)线程要求的条件:转出账户和转入账户都没有被分配过;

      3)何时等待:线程要求的条件不满足就等待;

      4)何时通知:当有线程释放账户时就通知;

      7-2小记