java并发队列专题开启(干货收藏)

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

写在前面

说到程序员必备知识,那不得不提java队列,这是基础,更是掌握高阶知识点的梯子。接下来我会通过一篇大专题一起来学习下,专题已经创建好,欢迎点击我头像进入专题学习系列文章。话不多说,进入今天的学习。

队列

Queue,看到这个词那就是队列,怎么理解呢,队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。,可能用的比较多的是LinkedList,LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

看下图理解下(图片来源于网路)

image.png

针对队列我们大致分为以下几种:

image.png

我们今天主要讲下阻塞队列当中的一种类,后续会精讲每一种阻塞队列。

阻塞队列

阻塞队列可分为以下几种:

image.png

阻塞队列(Blocking Queue)提供了可阻塞的 put 和 take 方法,它们与可定时的 offer 和 poll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。

image.png

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

下面拿代码深入理解下

代码展示(ArrayBlockingQueue)

今天的主角是阻塞队列ArrayBlockingQueue,我用生产者和消费者的模式来说下。先来说下ArrayBlockingQueue其中几个比较重要的方法

  • void put(E e):插入元素,如果队列满则一直等待。插null会抛出NullPointerException,等待的时候被打断会抛出InterruptedException

  • boolean offer(E e,long timeout,TimeUnit unit):插入元素,成功返回true,队列满则不插入也不等待,返回false。

  • E take():拿出对头的元素,返回该元素,队列空一直等待,和put对应。

/**
* BlockingQueue的使用
* 生产者,模拟put数据
* 实现Runnable 开启线程调用
* @author yn
*/
public class Producer implements Runnable {
 
        //定义队列
	private  BlockingQueue<Integer> blockingQueue;
	private static int element = 0;
	
	public Producer(BlockingQueue<Integer> blockingQueue) {
	       this.blockingQueue = blockingQueue;
	}
 
        //模拟put进数据
	public void run() {
		try {
                        //循环20次。19 
                        //生产数字,从0一直到19,然后就停工了,中间如果消费者来不及消费,生产者会自动阻塞。
                        while(element < 20) {
				System.out.println("将要放进去的元素是:"+element);
				blockingQueue.put(element++);
			}	
		} catch (Exception e) {
			System.out.println("生产者在等待空闲空间的时候被打断了!");
			e.printStackTrace();
		}
		System.out.println("生产者已经终止了生产过程!");
	}
}
复制代码

创建消费者

/**
* BlockingQueue的使用
* 消费者,take数据
* 实现Runnable 开启线程调用
* @author yn
*/
public class Consumer implements Runnable {
 
	private  BlockingQueue<Integer> blockingQueue;
	
	public Consumer(BlockingQueue<Integer> blockingQueue) {
		this.blockingQueue = blockingQueue;
	}
 
 
	public void run() {
		try {
                        //消费者一直在消费,queue空的时候自动等待,即使生产者停止了生产,消费者也会等待。
			while(true) {
				System.out.println("取出来的元素是:"+blockingQueue.take());
			}
		} catch (Exception e) {
			System.out.println("消费者在等待新产品的时候被打断了!");
			e.printStackTrace();
		}
	}
}
复制代码

创建测试类,我新建了大小为3的队列,把这个队列传给生产者和消费者,它们公用这个队列,满的时候生产者阻塞,空的时候消费者阻塞,然后开启生产者和消费者进程。

public class MainClass {
	public static void main(String[] args) {
                //ArrayBlockingQueue 
		BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(3,true);
		Producer producerPut = new Producer(blockingQueue);
		Consumer consumer = new Consumer(blockingQueue);
		ProducerOffer producerOffer = new ProducerOffer(blockingQueue);
		//生产put数据
		new Thread(producerPut).start();
		//消费take数据
		new Thread(consumer).start();	
	}
}
复制代码

结果(每次都会不一样,但是顺序是一样的,每次都是按照顺序取的)

将要放进去的元素是:0
将要放进去的元素是:1
将要放进去的元素是:2
取出来的元素是:0
将要放进去的元素是:3
取出来的元素是:1
将要放进去的元素是:4
取出来的元素是:2
将要放进去的元素是:5
取出来的元素是:3
将要放进去的元素是:6
取出来的元素是:4
将要放进去的元素是:7
取出来的元素是:5
将要放进去的元素是:8
将要放进去的元素是:9
取出来的元素是:6
将要放进去的元素是:10
取出来的元素是:7
将要放进去的元素是:11
取出来的元素是:8
将要放进去的元素是:12
将要放进去的元素是:13
取出来的元素是:9
取出来的元素是:10
将要放进去的元素是:14
取出来的元素是:11
将要放进去的元素是:15
取出来的元素是:12
将要放进去的元素是:16
取出来的元素是:13
将要放进去的元素是:17
取出来的元素是:14
将要放进去的元素是:18
取出来的元素是:15
将要放进去的元素是:19
取出来的元素是:16
取出来的元素是:17
生产者已经终止了生产过程!
取出来的元素是:18
取出来的元素是:19
复制代码

生产者最多连续生产3次,然后队列满了,要等待消费者消费,消费者同理。

image.png

多说一句,通常情况下为了保证公平性会降低吞吐量。我们可以使用构造函数创建一个公平的阻塞队列,设置ReentrantLock的锁模式为公平锁构造函数。

总结

ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。

弦外之音

OK 今天的讲解就到这里,后续我会继续完善各种专题,本章节也会收到到我的专题里面,欢迎大家讨论学习。

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

聊聊不一样的策略模式(值得收藏)

copy对象,这个操作有点骚!

猜你喜欢

转载自juejin.im/post/7017392636541698055

相关文章