阻塞队列原理和线程池源码浅析

本文深入探讨了Java中的阻塞队列,包括ArrayBlockingQueue和LinkedBlockingQueue,阐述了它们在多线程和线程池中的应用。接着分析了线程池的构建、状态转换和submit任务的处理流程,详细讲解了ExecutorService和RejectedExecutionHandler的使用,最后讨论了线程池的生命周期管理与不同类型的线程池生成方式。
摘要由CSDN通过智能技术生成

阻塞队列

非阻塞队列是一个先进先出的单向队列(Queue),而BlockingQueue阻塞队列实际是非阻塞队列的一个加强,之所以是阻塞队列最主要的是实现take和put,当阻塞队列满时,put会一直阻塞直到拿到数据,或者响应中断退出;当队列空时,take元素,队列也会阻塞直到队列可用。

阻塞的意思就是此条线程会处于等待卡死状态,解锁的条件是队列中有另一条线程存入或取出数据了,就会解锁,就相当于队列是仓库,仓库没有货了就生产,有货就能消费,锁条件是notFull和notEmpty。

Throws exception Special value Blocks Times out
插入方法 add(e) offer(e) put(e) offer(e, time, unit)
移除方法 remove() poll() take() poll(time, unit)
检查方法 element() peek()

队列(Queue和Deque)

队列是一种特殊的线性表,链表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。

进行插入操作的端称为队尾,进行删除操作的端称为队头。

单向队列(Queue):先进先出(FIFO),只能从队列尾插入数据,只能从队列头删除数据.

双向队列(Deque):可以从队列尾/头插入数据,只能从队列头/尾删除数据.操作头和尾.

单向队列Queue结构

private Node first;
private Node last;
int size = 0;

class Node{
    private Node next;
    private Object element;

    public Node(Object element) {
        this.element = element;
    }
    public Node(){}
}

单向队列操作

public void push(Object element){
    //单向队列(Queue):先进先出(FIFO),只能从队列尾插入数据
    size++;
    Node node = new Node(element);
    if (size>1){
        this.last.next = node;
        this.last = node;
    }else if (size == 1){
        this.first = node;
        this.last = node;
    }
}

public void pull(){
    //单向队列(Queue):先进先出(FIFO),只能从队列头删除数据.
    size--;
    this.first = this.first.next;
    if (size == 1){
        this.last = this.first;
    }
}

常见的阻塞队列

阻塞队列:BlockingQueue,多用于创建线程池

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列,只维护一个lock。

线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列,维护两个lock。

如果不设置队列容量,其初始化的默认容量大到可以认为是无界队列了,在这种情况下maximumPoolSize参数是无效的,队列中的任务太多,以至于由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

DelayedWorkQueue:ScheduledThreadPoolExecutor静态内部类的一个无界延迟阻塞队列,添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被执行

SynchronousQueue:无存储空间的阻塞队列。

1.SynchronousQueue是一个双栈双队列算法,无空间的队列,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。
2.提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据设置的handler执行拒绝策略。因此这种方式提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,需要对程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

非阻塞队列的API

除了阻塞队列就是非阻塞队列,非阻塞队列,就是单纯意义上的"非"阻塞队列,其没有put和take方法,可以无限制存,且是线程安全的,N个用户同时存也能保证每次存放在队尾而不乱掉,但是其的size()方法会不定时遍历,所以很耗时

以ConcurrentLinkedQueue并发队列为例,多用于消息队列,并发异步处理,如日志等;add和poll是线程安全的,但是其他方法并没有保证线程安全,如判断isEmpty(),所以在多线程并发时还得自己加锁

ConcurrentLinkedQueue内部就是一个简单的单链表结构,每入队一个元素就是插入一个Node类型的结点。head指向队列头,tail指向队列尾,通过Unsafe来CAS操作字段值以及Node对象的字段值

offer/add添加队列元素:两个方法一样,都是将指定元素插入此队列的尾部,添加成功返回true;区别在于队列满时,add会抛异常,而offer会返回false

poll获取并移除此队列的头,如果此队列为空,则返回 null

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    //先进先出
    System.out.println(queue.poll());//1000
    System.out.println(queue.poll());//2000
    System.out.println(queue.poll());//null
}

peek获取但不移出队列的头,如果此队列为空,则返回 null

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    //peek获取但不移除队列的头,所以每次peek()都是1000
    System.out.println(queue.peek());//1000
    System.out.println(queue.peek());//1000
    System.out.println(queue.poll());//1000
}

remove从队列中移除指定元素,已存在元素,会返回true,remove不存在元素,返回false

contains判断当前队列是否包含指定元素,如果存在返回true,不存在返回false

public static void main(String[] args) {
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
    queue.add("1000");
    queue.offer("2000");
    System.out.println(queue.remove("1000"));//true
    //1000已经被移除了
    System.out.println(queue.remove("1000"));//false
    System.out.println(queue.contains("1000"));//false
    System.out.println(queue.contains("2000"));//true
}

size队列的数量,如果此队列包含的元素数大于 Integer.MAX_VALUE,则只会返回 Integer.MAX_VALUE。由于这些队列的异步特性,确定当前的元素数需要进行一次花费 O(n) 时间的遍历。所以在需要判断队列是否为空时,使用peek()!=null,不要用 queue.size()>0

isEmpty判断当前队列是否为null

**toArray(T[] a)**转为指定数组,按照头->尾的顺序返回

ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
queue.add("1000");
queue.offer("2000");
queue.offer("3000");
Object[] array = queue.toArray();
for (Object o : array){
    System.out.println(o);1000->2000->3000
}

iterator获取按照头到尾的顺序遍历的迭代器

ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();
queue.add("1000");
queue.offer("2000");
Iterator<Object> iterator = queue.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

队列应用的示例:

    //创建并发队列
    ConcurrentLinkedQueue<Object> queue = new ConcurrentLinkedQueue<>();

    //提供100000份需要消耗的食物,并放入队列
    int productionNum = 10000;
    for(int i = 1;i<10001;i++){
   
        queue.offer("食物编号:"+i);
    }

    ThreadPoolExecutor executorService = (ThreadPoolExecutor)Executors.newFixedThreadPool(100);
    for (int i =0;i<10;i++){
   
        //submit()有返回值,而execute()没有
        Future<?> future = executorService.submit(new Runnable() {
   
            @Override
            public void run() {
   
                while (!queue.isEmpty()) {
   
                    System.out.println("当前线程为:" + Thread.currentThread().getName() + ":" + queue.poll());
                }
            }
        });
    }

ArrayBlockQueue/LinkedBlockQueue

ArrayBlockingQueue基于数组不会产生或销毁任何额外的对象实例,LinkedBlockingQueue基于链表会生成额外的Node对象会有会内存溢出的风险。但是常用的其实还是LinkedBlockingQueue,使用两套锁,实现生产和消费操作的并行,单个锁只能保证生产者和消费者只能每次操作一次生产或者消费,而双锁可以使得生产者在队列满时不断的向队列尾部添加node,消费者不断从head获取Node,从而吞吐效率更高

//Array可以选择公平锁和非公平锁,而Linked两把锁都是非公平锁
BlockingQueue<String> arrayQueue = new ArrayBlockingQueue<>(100,true);
BlockingQueue<String> linkedQueue = new LinkedBlockingQueue<>(100);
//阻塞的方法只有take和put
queue.take();
queue.put("");

ArrayBlockingQueue源码:

//数组 
final Object[] items;
//获取数据的索引,主要用于take,poll,peek,remove方法
int takeIndex;
//添加数据的索引,主要用于 put, offer, or add 方法
int putIndex;
//队列元素的个数
int count;
final ReentrantLock lock;
//notEmpty条件对象,用于通知take方法队列已有元素,可执行获取操作
private final Condition notEmpty;
//notFull条件对象,用于通知put方法队列未满,可执行添加操作
private final Condition notFull;

put方法

public void put(E e) throws InterruptedException {
   
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //可中断加锁
    lock.lockInterruptibly();
    try {
   
        //数组满了,生产条件释放锁并阻塞线程
        while (count == items.length)
            notFull.await();
        //入队操作
        enqueue(e);
    } finally {
   
        lock.unlock();
    }
}

private void enqueue(E x) {
   
    final Object[] items = this.items;
    //设置当前索引值为put添加的值,初始为0
    items[putIndex] = x;
    //length是固定的,简单理解就是当条件这个元素之后数组就满了
    //那么当消费者从0开始往后消费,生产者被唤醒从而继续从0开始继续添加
    //需要先了解消费的方式:会从头一直消费到最后一个元素之后又从0开始继续消费
    if (++putIndex == items.length)
        //消费者从0往后依次消费,生产者在从0开始继续添加
        putIndex = 0;
    count++;
    //唤醒消费者
    notEmpty.signal();
}

take方法

public E take() throws InterruptedException {
   
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
   
        //数组没有资源,消费者进入休眠
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
   
        lock.unlock();
    }
}

private E dequeue() {
   
    final Object[] items = this.items;
	//takeIndex初始也为0,从索引0开始消费
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    //take索引自增,当消费完最大的索引值,又从0开始消费
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //消费之后数组未满,所以唤醒生产者继续生产
    notFull.signal();
    return x;
}

LinkedBlockingQueue源码:

//由Node单向链表组成的队列,初始化时创建一个空Node并且设置为head和last
static class Node<E> {
   
    E item;
    Node<E> next;
    Node(E x) {
    item = x; }
}
//队列容量
private final int capacity;
//队列长度,因为多个线程可以同时生产或消费,需要保证改变值的可见性和原子性
private final AtomicInteger count = new AtomicInteger();
//队列的头和尾
transient Node<E> head;
private transient Node<E> last;
// 生产者和消费者维护了两套锁
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

put方法,可以实现入列和消费同时进行,但是生产或者消费时只能同时运行一个线程

public void put(E e) throws InterruptedException {
   
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    //生产锁
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
   
        //如果满了,当前生产线待机
        while (count.get() == capacity) {
   
            notFull.await();
        }
        //入列
        enqueue(node
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值