1.BlockingQueue 线程池阻塞队列

java 线程池为何要使用阻塞队列 java 线程池 阻塞队列_java 线程池为何要使用阻塞队列

阻塞队列方法

public interface BlockingQueue<E> extends Queue<E> {
    boolean add(E e);

    boolean offer(E e);

    
    void put(E e) throws InterruptedException;

   
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

   
    E take() throws InterruptedException;

   
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

   
    int remainingCapacity();

    
    boolean remove(Object o);

   
    public boolean contains(Object o);

    
    int drainTo(Collection<? super E> c);

    
    int drainTo(Collection<? super E> c, int maxElements);
}

java 线程池为何要使用阻塞队列 java 线程池 阻塞队列_面试_02

入队:

  1. offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false–>不阻塞
  2. put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断–>阻塞
  3. offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:–> 阻塞

被唤醒
等待时间超时
当前线程被中断

出队:

  1. poll():如果没有元素,直接返回null;如果有元素,出队
  2. take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断–>阻塞
  3. poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:

被唤醒
等待时间超时
当前线程被中断

(1) ArrayBlockingQueue 先进先出有界循环数组 线程安全

  • 利用有限数组存放任务,不支持扩容,每次只允许一个线程获取或者添加
  • 利用AQS Lock 来实现阻塞获取,阻塞添加,线程安全,支持公平锁和非公平锁
  • 循环数组队列,takeInde putIndex 作为头尾指针,count为现在拥有的数量
public class ArrayBlockingQueue<E> extends AbstractQueue<E>  implements BlockingQueue<E>, java.io.Serializable {
       final Object[] items;//存放元素的数组
       int takeIndex;//记录元素被取出元素的数组下标
       int putIndex;//记录元素被放入元素的数组下标
       int count;//记录元素的个数
       //可以选择公平锁或者非公平锁
       final ReentrantLock lock;//锁,同样也是保证多线程安全的一个重要因素
       
       //notEmpty是当前lock的阻塞队列,
       //作用就是采用内部的一个Condition队列来存储想通过put进行添加元素,但由于数组已满而被阻塞的线程。
       private final Condition notEmpty;
      
      //notFull是当前lock的另一个阻塞队列
      //作用就是采用内部的Condition队列来存储想通过take进行取出元素,但由于数组为空而被阻塞的线程。        
      private final Condition notFull;
       
       transient Itrs itrs = null;
       
       public ArrayBlockingQueue(int capacity, boolean fair) {
            if (capacity <= 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity];
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
        }
}

添加方法:

//非阻塞添加方法,没有就立即返回
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

//如果队列满了,阻塞等待别人唤醒。notFull 没有满唤醒。
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 E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

//队列已满,使用超时时间等待, 要么时间到,要么被唤醒,要么被中断。
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

移除方法:

//非阻塞获取,数组没有就立即返回
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

//阻塞获取,数组没有就阻塞等待唤醒
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
//加入队列
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    notEmpty.signal();
}

//队列已满,使用超时时间等待, 要么时间到,要么被唤醒,要么被中断。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

(2) LinkedBlockingQueue 单向有限阻塞队列(两个锁:入队锁和出队锁)

  • LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队
  • 虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行
  • LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的。
  • 默认是Integer.MAX_VALUE,有容量危险,而且每次加入都要重新去new,不像ArrayBlockingQueue是提前分配好的。
  • 在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
            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();
}

阻塞放进队列:(看一下两个不同的锁如何控制入队和出队)

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,其他流程跟ArrayBlockingQueue 类似。
    putLock.lockInterruptibly();
    try {
       
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

阻塞获取队列:

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
       //使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

(3) LinkedBlockingDeque 双向链表实现的有界双向并发阻塞队列

  • 使用双向链表实现有界阻塞队列
public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {
    //头节点
    transient Node<E> first;

    //尾巴节点
    transient Node<E> last;
    
    //节点数量
    private transient int count;
    
   //节点数量边界
    private final int capacity;
    
    final ReentrantLock lock = new ReentrantLock();
    
    
    private final Condition notEmpty = lock.newCondition();
    
   
    private final Condition notFull = lock.newCondition();
    
    
    public LinkedBlockingDeque(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
    }
}

获取方法:原理跟ArrayBlockingQueue一样的,不做多分析

public E take() throws InterruptedException {
    return takeFirst();
}

public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ( (x = unlinkFirst()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}

  private E unlinkFirst() {
        // assert lock.isHeldByCurrentThread();
        Node<E> f = first;
        if (f == null)
            return null;
        Node<E> n = f.next;
        E item = f.item;
        f.item = null;
        f.next = f; // help GC
        first = n;
        if (n == null)
            last = null;
        else
            n.prev = null;
        --count;
        //被外界获取走了,腾出空位,让外界放入
        notFull.signal();
        return item;
    }

2.拒绝策略

线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池

java 线程池为何要使用阻塞队列 java 线程池 阻塞队列_java_03

//执行任务拒绝的时候,调用拒绝策略的方法
final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}


//拒绝策略接口
public interface RejectedExecutionHandler {
    //拒绝策略
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

(1) CallerRunsPolicy

由提交任务的主线程自己完成,如果完成不了,无法执行后面的任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {
   
    public CallerRunsPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //由调用线程完成处理该任务。这种情况需要让所有任务都执行完毕
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

(2) AbortPolicy

丢弃任务并抛出异常。这是默认的拒绝策略

public static class AbortPolicy implements RejectedExecutionHandler {
    
    public AbortPolicy() { }

    //丢弃任务并抛出异常。这是默认的拒绝策略
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

(3) DiscardPolicy

丢弃任务,但是不抛出异常

public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardPolicy}.
     */
    public DiscardPolicy() { }

    //丢弃任务,但是不抛出异常
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

(4) DiscardOldestPolicy

丢弃队列最前面的任务,重新提交给拒绝的任务

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
   
    public DiscardOldestPolicy() { }

    //丢弃队列最前面的任务,重新提交给拒绝的任务
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

3. Executors

java 线程池为何要使用阻塞队列 java 线程池 阻塞队列_开发语言_04

java 线程池为何要使用阻塞队列 java 线程池 阻塞队列_java_05

(1) newCachedThreadPool()线程池 (推荐)

  • 核心线程数:0
  • 最大线程数:Integer.MAX_VALUE
  • 阻塞队列:SynchronousQueue,不会存储元素,相当于没有队列

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

(2) newSingleThreadExecutor (阻塞队列长度无限,容易OOM)

  • 核心线程数:1
  • 最大线程数:1
  • 阻塞队列:LinkedBlockingQueue,无限队列

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

(3) newFixedThreadPool 创建固定线程数的(最小=最大)

  • 核心线程数:n
  • 最大线程数:n
  • 阻塞队列:LinkedBlockingQueue,无限队列

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

(4) newScheduledThreadPool

  • 核心线程数:n
  • 最大线程数:Integer.MAX_VALUE
  • 阻塞队列:DelayedWorkQueue 无界队列,指定多久从队列获取任务

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}