今天来看生产者与消费者模型实现最终优化方案,也是最流行的一种实现,即采用LinkedBlockingQueue的实现方式:
开始调试:
main方法中与之前的不同之处是产品仓库采用LinkedBlockingQueue模拟了一个100容量的仓库,其他没有区别,直接进入生产者线程:
此处与之前的优化方案不同之处在于产品放入仓库的方法,直接将产品存入LinkedBlockingQueue实现的仓库中,外面没有用到任何同步锁,那么锁的实现肯定在put方法里面,跟进去看看:
此处在方法内部实现了一个重入锁putLock,重入锁的作用是获得锁的当前线程可以重新再进入该锁,该锁的lockInterruptibly方法可以让当前线程获取到锁除非当前线程被打断,当前线程再次进入该锁会使重入锁保存的当前线程数量加一,该锁的unlock方法可以让当前线程释放锁,具体实现不细述,可以把他看成一个同步锁,锁的边界用这个两个方法来界定,在锁的作用域内实现了往该阻塞队列即仓库的存放产品过程,满了就让生产者等待,此处是调用notFull.await方法,看看notFull的定义:
即等待调用puts方法的线程的等待队列,类似还有notEmpty,即等待调用gets方法的线程的等待队列,简单的讲就是存放线程的队列,notFull.await方法指让所有想要调用LinkedBlockingQueue的put方法进行元素存放的线程进行等待,因为当前LinkedBlockingQueue已满,如果LinkedBlockingQueue未满,就让一个元素入队并将元素数量加一,此处应用原子类来进行自增操作,估计是防止可以重进入的线程造成对元素数量的并发修改,存放完成后如果此时队列还有容量,就给调用LinkedBlockingQueue的put方法的线程发信号,让其进行put操作,即让生产者继续生产,随后把锁释放,让这些生产者线程能够有锁可用。如果队列中一开始就没有元素(c初始值为-1,加一后为0),说明有调用LinkedBlockingQueue的take方法的线程,即消费者线程在等待,就要调用signalNotEmpty方法:
此处先获取takeLock,保证没有线程持有取出元素需要的锁,即消费者的锁,然后唤醒调用LinkedBlockingQueue的take方法的线程,即消费者线程,让其继续消费,最后释放锁给消费者线程使用。
看完生产者的线程,再看消费者线程:
与生产者线程类型,不同在于queue的take方法,进去看看:
原理与put方法一样,只不过这里保持了一个takeLock——从队列中取走元素需要的锁,即消费者锁,其他可以对比着看,不再赘述。
由上面的调试可以看出,这个优化方案在生产者往仓库放入产品和消费者从仓库取出产品的过程中各加了一把锁,两把锁各自在队列的队头和对尾同步存线程和取线程,这样这两种操作仓库的方式就不会相互阻塞,可以同时执行存和取线程,只有存线程与存线程,取线程和取线程直接需要同步,而且生产和消费活动也不会被锁阻塞,这种优化方式就可以较高的提升效率。
以上就是生产者与消费者模型实现的第三种方案,至此,生产者与消费者模型实现的调试就此结束。