您现在的位置:诗歌范文 > 西方诗歌

深入浅出 Java Concurrency (21) 并发容器 part 6 可阻塞的BlockingQueue (1)

时间:2019-08-13 11:27   编辑:本站

	深入浅出 Java Concurrency (21) 并发容器 part 6 可阻塞的BlockingQueue (1)

针对第2点,特别补充说明下。 本来这属于锁机制中条件队列的范围,由于没有应用场景,所以当时没有提。

前面提高notifyall总是比notify更可靠,因为notify可能丢失通知,为什么不适用notifyall呢?先解释下notify丢失通知的问题。 notify丢失通知问题假设线程A因为某种条件在条件队列中等待,同时线程B因为另外一种条件在同一个条件队列中等待,也就是说线程A/B都被同一个()挂起,但是等待的条件不同。

现在假设线程B的线程被满足,线程C执行一个notify操作,此时JVM从()的多个线程(A/B)中随机挑选一个唤醒,不幸的是唤醒了A。

此时A的条件不满足,于是A继续挂起。

而此时B仍然在傻傻的等待被唤醒的信号。

也就是说本来给B的通知却被一个无关的线程持有了,真正需要通知的线程B却没有得到通知,而B仍然在等待一个已经发生过的通知。 如果使用notifyall,则能够避免此问题。

notifyall会唤醒所有正在等待的线程,线程C发出的通知线程A同样能够收到,但是由于对于A没用,所以A继续挂起,而线程B也收到了此通知,于是线程B正常被唤醒。 既然notifyall能够解决单一notify丢失通知的问题,那么为什么不总是使用notifyall替换notify呢?假设有N个线程在条件队列中等待,调用notifyall会唤醒所有线程,然后这N个线程竞争同一个锁,最多只有一个线程能够得到锁,于是其它线程又回到挂起状态。

这意味每一次唤醒操作可能带来大量的上下文切换(如果N比较大的话),同时有大量的竞争锁的请求。

这对于频繁的唤醒操作而言性能上可能是一种灾难。 如果说总是只有一个线程被唤醒后能够拿到锁,那么为什么不使用notify呢?所以某些情况下使用notify的性能是要高于notifyall的。 如果满足下面的条件,可以使用单一的notify取代notifyall操作:相同的等待者,也就是说等待条件变量的线程操作相同,每一个从wait放回后执行相同的逻辑,同时一个条件变量的通知至多只能唤醒一个线程。

也就是说理论上讲在put/take中如果使用sinallAll唤醒的话,那么在清单2中的就是多余的。 出队列过程(poll/take)再来看出队列过程。 清单3描述了出队列的过程。

可以看到这和入队列是对称的。

从这里可以看到,出队列使用的是和入队列不同的锁,所以入队列、出队列这两个操作才能并行进行。 清单3阻塞的出队列过程publicEtake()throwsInterruptedException{Ex;intc=-1;finalAtomicIntegercount=;finalReentrantLocktakeLock=;();try{try{while(()==0)();}catch(InterruptedExceptionie){();//propagatetoanon-interruptedthreadthrowie;}x=extract();c=();if(c1)();}finally{();}if(c==capacity)signalNotFull();returnx;}为什么有异常?有了入队列、出队列的过程后再来回答前面的几个问题。 为什么总是抛出InterruptedException异常?这是很大一块内容,其实是Java对线程中断的处理问题,希望能够在系列文章的最后能够对此开辟单独的篇章来谈谈。

在锁机制里面也是总遇到,这是因为,Java里面没有一种直接的方法中断一个挂起的线程,所以通常情况下等于一个处于WAITING状态的线程,允许设置一个中断位,一旦线程检测到这个中断位就会从WAITING状态退出,以一个InterruptedException的异常返回。 所以只要是对一个线程挂起操作都会导致InterruptedException的可能,比如()、()、()。 尽管()不会抛出一个InterruptedException异常,但是它会将当前线程的的interrupted状态位置上,而对于Lock/Condition而言,当捕捉到interrupted状态后就认为线程应该终止任务,所以就抛出了一个InterruptedException异常。 又见volatile还有一个不容易理解的问题。

为什么是volatile类型的?起初我不大明白,因为对于一个进入队列的Node,它的item是不变,当且仅当出队列的时候会将头结点元素的item设置为null。

尽管在remove(o)的时候也是设置为null,但是那时候是加了putLock/takeLock两个锁的,所以肯定是没有问题的。

那么问题出在哪?我们知道,item的值是在put/offer的时候加入的。 这时候都是有putLock锁保证的,也就是说它保证使用putLock锁的读取肯定是没有问题的。

那么问题就只可能出在一个不适用putLock却需要读取的地方。 peek操作时获取头结点的元素而不移除它。

显然他不会操作尾节点,所以它不需要putLock锁,也就是说它只有takeLock锁。

清单4描述了这个过程。

清单4查询队列头元素过程publicEpeek(){if(()==0)returnnull;finalReentrantLocktakeLock=;();try{NodeEfirst=;if(first==null)returnnull;;}finally{();}}清单4描述了peek的过程,最后返回一个非null节点的结果是。

这里读取了Node的item值,但是整个过程却是使用了takeLock而非putLock。 换句话说putLock对的操作,peek()线程可能不可见!清单5队列尾部加入元素privatevoidinsert(Ex){last==newNodeE(x);}清单5是入队列offer/put的一部分,这里关键在于last=newNodeE(x)可能发生重排序。

Node构造函数是这样的:Node(Ex){item=x;}。 在这一步里面我们可能得到以下一种情况:。

  猜您喜欢的文章