阻塞队列实现生产者消费者以及同步工具类

要学习多线程一些基本的同步类也是不得不学习的,这里主要讲一点基本的概念与使用 。
阻塞队列阻塞队列提供可阻塞的put和take方法,支持定时的offer和poll方法,如果队列已经满了,那么put方法将阻塞直到有空间可用;如果队列为空,那么take方法将会阻塞直到有元素可用;同时队列可以是有界也可以是无界的,无界队列永远都不会充满,因此无界队列的put方法永远不会阻塞;
JAVA中的阻塞队列主要有以实现BlockingQueue接口的几个类,其中LinkedBlockingQueue、ArrayBlockingQueue对应LinkedList与ArrayList,他们是先进先出队列(FIFO);阻塞队列能够实现生产者与消费者的关系,生产者往队列中put数据,而消费者往take中拉取数据,进而实现异步操作 。曾经的生产中的例子简化后如下图:

阻塞队列实现生产者消费者以及同步工具类

文章插图
 
队列还有一些实现比如:
PriorityBlockingQueue队列是优先级队列,根据元素的比较来控制顺序;
SynchronousQueue队列维护的不是元素而是线程,这些线程在等待把元素加入或者移除队列,就好像餐馆出菜,前面的队列都是把菜炒好了放到放菜的地方,服务员一个一个拿走,而SynchronousQueue则没有放菜的地方,厨师炒好菜直接给服务员端走,所以SynchronousQueue要求消费者足够多,并且总是有至少一个消费者在等待才适合用 。
同步工具类闭锁
闭锁:可以延迟线程的进度直到其到达终止状态 。闭锁相当于一个闸门,在闭锁到达结束状态之前,这个闸门一直是关闭的,任何线程都不能通过当到达结束状态时,闸门允许所有线程通过,闭锁到达结束状态后不会再改变状态闭锁 。
闭锁主要作用是可以用来确保一些活动直到其他活动都完成后才继续执行 。比如一个初始化需要等另一个初始化,比如所有玩家都准备才开始 。
CountDownLatch是一种闭锁实现,它初始化一个正数,countDown方法使数字递减,await方法使线程等待计数器到0,否则阻塞,示例如下图:
阻塞队列实现生产者消费者以及同步工具类

文章插图
 
这个方法主要用来统计一个任务在多个线程同步执行下消耗时间,利用两个闭锁实现了线程先一起准备好,然后放开开始闭锁,所有线程一起执行,每个线程最后都会countDown一下结束闭锁,当所有线程执行完成结束闭锁也就方法,这样就可以实现统计所有线程一起执行消耗的时间 。
FutureTask
FutrueTask是一个实现了Runnable的类,并且它可以获取到线程的执行结果,get方法执行取决于任务的状态,如果任务已经完成,那么get会立即返回结果,否则get将
阻塞直到任务进入完成状态,可以利用它的这个功能来实现闭锁 。
它还可以用来异步提前计算一些比较耗性能的值,比如一个操作比较耗性能那么可以让它先执行,等到需要计算的结果的时候在get 。
信号量
计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量 。
实现原理是:Semaphore管理着一组虚拟的许可,执行操作前必须先acquire获得许可,这个许可就减一,如果许可被减到0,线程再来获取许可就阻塞等待其他线程在操作完成后release释放许可;
比如初始化了一定数量的数据库线程池,每个线程获取线程池时先acquire,如果获取成功说明有线程继续执行,如果没有线程了acquire方法会阻塞等待有多余线程 。
还可以把计数信号量的数量设置为1,那么只要有一个线程acquire成功,其他线程就只能等待直到线程release,这样就实现了一个互斥锁 。
栅栏
栅栏类似于闭锁,他也是阻塞一组线程直到某个事件发生,区别在于栅栏是等待所有线程到达指定,而闭锁是等待一个事件,闭锁是一次性的不会重置 。闭锁是等待其他线程执行完成等待线程才开始执行,而栅栏是让每个线程都等待,直到所有线程都是等待状态,那么所有线程一起继续执行 。
可能有点不好理解,通过下面一个例子和闭锁例子进行对比,示例如下图:
阻塞队列实现生产者消费者以及同步工具类

文章插图
 
不管执行多少次,一定是所有的"开始"打印完成后才会打印"结束",并且如果线程的数量没有初始化栅栏的值多,所有线程都会处于阻塞状态 。
总结今天总结了一点阻塞队列的知识和应用,平时一些消耗性能而创建线程去异步执行可能会造成线程太多的情况,可以简单的通过阻塞队列来实现生产者消费者的方式来解决问题 。


推荐阅读