Java 并发编程(实战)
在并发程序中,当一个线程进入临界区后,由于某些条件不满足,需要进入等待状态, Java 对象的 wait() 方法就能够满足这种需求。如上图所示,当调用 wait() 方法后,当前线程就会被阻塞,并且进入到右边的等待队列中,这个等待队列也是互斥锁的等待队列。 线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获得锁,并进入临界区了。
那线程 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 要求的条件满足时,该怎么通知这个等待的线程呢?很简单,就是 Java 对象的 notify() 和 notifyAll() 方法。我在下面这个图里为你大致描述了这个过程,当条件满足时调用 notify(),会通知等待队列(互斥锁的等待队列)中的线程,告诉它条件曾经满足过。
为什么说是曾经满足过呢?因为 notify() 只能保证在通知时间点,条件是满足的。 而被通知线程的执行时间点和通知的时间点基本上不会重合,所以当线程执行的时候,很可能条件已经不满足了(保不齐有其他线程插队)。这一点你需要格外注意。
除此之外,还有一个需要注意的点,被通知的线程要想重新执行,仍然需要获取到互斥锁(因为曾经获取的锁在调用 wait() 时已经释放了)。
上面我们一直强调 wait()
、notify()
、notifyAll()
方法操作的等待队列是互斥锁的等待队列,所以如果 synchronized 锁定的是 this,那么对应的一定是 this.wait()
、this.notify()
、this.notifyAll()
;如果 synchronized 锁定的是 target,那么对应的一定是target.wait()
、target.notify()
、target.notifyAll()
。而且 wait()
、notify()
、notifyAll()
这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以我们会发现 wait()
、notify()
、notifyAll()
都是在 synchronized{}
内部被调用的。如果在 synchronized{}
外部调用,或者锁定的 this,而用 target.wait()
调用的话,JVM 会抛出一个运行时异常:java.lang.IllegalMonitorStateException
。
小试牛刀:一个更好地资源分配器
等待 - 通知机制的基本原理搞清楚后,我们就来看看它如何解决一次性申请转出账户和转入账户的问题吧。在这个等待 - 通知机制中,我们需要考虑以下四个要素。
互斥锁:上一篇文章我们提到 Allocator 需要是单例的,所以我们可以用 this 作为互斥锁。
线程要求的条件:转出账户和转入账户都没有被分配过。
何时等待:线程要求的条件不满足就等待。
何时通知:当有线程释放账户时就通知。
将上面几个问题考虑清楚,可以快速完成下面的代码。需要注意的是我们使用了:
while(条件不满足) {wait();}
利用这种范式可以解决上面提到的条件曾经满足过这个问题。因为当 wait() 返回时,有可能条件已经发生变化了,曾经条件满足,但是现在已经不满足了,所以要重新检验条件是否满足。范式,意味着是经典做法,所以没有特殊理由不要尝试换个写法。后面在介绍“管程”的时候,我会详细介绍这个经典做法的前世今生。
class Allocator {private Java 开源项目【ali1024.coding.net/public/P7/Java/git】 List<Object> als;// 一次性申请所有资源 synchronized void apply(Object from, Object to){// 经典写法 while(als.contains(from) || als.contains(to)){try{wait();}catch(Exception e){}}als.add(from);als.add(to);}// 归还资源 synchronized void free(Object from, Object to){als.remove(from);als.remove(to);notifyAll();}}
最后
手绘了下图所示的 kafka 知识大纲流程图(xmind 文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友
评论