Java线程池带实例讲解,阻塞队列说明

首先,线程池是啥,有啥好处这里就不提了.google一下马上知道. 嘻嘻嘻

首先第一步!我们先来认识下 在 java.util.concurrent 包中,提供了 ThreadPoolExecutor 的实现。
该类是核心,参数以及含义要多多理解并记下. 源代码如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

参数说明:

  • corePoolSize: 核心线程数,表示当前线程池主要工作的线程池数. 需要注意的是默认的初始线程数为0,当任务进来的时候才会慢慢的创建线程执行相应的任务,如果想一开始就创建所有的线程要调用prestartAllCoreThreads 方法
  • maximumPoolSize: 允许存在的最大线程数量,需要注意的是当核心线程数满了,并且阻塞队列也满了的情况下,才回去判断是否没有达到最大线程数然后决定是否去创建新的线程
  • keepAliveTime: 当线程数量多于核心线程数的时候,空闲的线程最多存活的时间
  • unit: keepAliveTime的时间单位
  • workQueue: 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。
  • threadFactory: 线程工厂
  • handler: 阻塞队列满并且线程数量已达到最大值,这时候要执行的策略:一般来讲有 终止,抛弃,抛弃最旧的,调用者运行

下面我使用一个线程池实现累加的操作讲解线程池的使用,并且打印出来当前活跃线程数以及执行完成的任务数量来帮助大家理解线程池的使用

  1. Executors.newCachedThreadPool()
package allen.interview.thread.pool.sourceCodeLearn;

import java.text.MessageFormat;
import java.util.concurrent.*;

/**
 * java.util.concurrent.Executors 包下的 newCachedThreadPool使用例子
 * 该例子场景描述: 简单的使用到信号量 做1000次加一操作
 *
 */
public class CachedThreadPoolExample {
    /**
     * 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数
     * return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
     *                                       60L, TimeUnit.SECONDS,
     *                                       new SynchronousQueue<Runnable>());
     *
     * 核心线程数:0
     * 最大线程数量:int最大值,也就是说基本不限制
     * 线程最大空闲时间:60
     * 空闲时间单位:seconds
     * 阻塞队列: SychronousQueue 一个不保存Runnable的队列,只有当有另外一个线程做取操作的时候才可以入队里,并且默认阻塞的线程
     * 是非公平形式的阻塞队列
     */
    private static ExecutorService cachedThreadPool=Executors.newCachedThreadPool();

    private static int count =0;

    public static void main(String[] args) throws InterruptedException {
        //信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
        Semaphore semaphore = new Semaphore(200);
        //倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这1000个线程每个都报数了(也就是执行countDown方法)
        CountDownLatch countDownLatch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            //!!!!重点来了,向我们的线程池提交一个Runnable的run方法,首先看线程池中有没有空闲的已创建的线程
            //如果有使用现成的线程, 如果没有则创建新的线程来执行
            cachedThreadPool.submit(() -> {
                try {
                    semaphore.acquire();//获取到信号量才可以进行加1操作
                    Thread.sleep(1000);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
                    add();
                    semaphore.release();//执行完释放信号量
                    countDownLatch.countDown();//不要忘了报数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            int threadCount = ((ThreadPoolExecutor)cachedThreadPool).getActiveCount();
            long taskCount = ((ThreadPoolExecutor)cachedThreadPool).getTaskCount();
            System.out.println(MessageFormat.format("当前活跃线程数 :{0}", String.valueOf(threadCount)));
            System.out.println(MessageFormat.format("已完成任务 :{0}", String.valueOf(taskCount)));
        }
        countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码  自己可以注释掉试下
        System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
        cachedThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
    }

    /**
     * 加上synchronized关键字保证
     */
    public static synchronized void add() {
        count++;
    }

}
  1. Executors.newFixedThreadPool(5)
package allen.interview.thread.pool.sourceCodeLearn;

import java.text.MessageFormat;
import java.util.concurrent.*;

/**
 * java.util.concurrent.Executors 包下的 newFixedThreadPool使用例子
 * 该例子场景描述: 简单 做1000次加一操作
 *
 */
public class FixedThreadPoolExample {
    /**
     * 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
     *  return new ThreadPoolExecutor(nThreads, nThreads,
     *                                       0L, TimeUnit.MILLISECONDS,
     *                                       new LinkedBlockingQueue<Runnable>());
     * 核心线程数: 必须设定的参数
     * 最大线程数量:和核心线程数相同
     * 线程最大空闲时间:0
     * 空闲时间单位:milliSeconds
     * 阻塞队列: LinkedBlockingQueue 一个不限制大小的队列(int 最大值的容量)
     */
    private static ExecutorService fixedThreadPool =Executors.newFixedThreadPool(5);

    private static int count =0;

    public static void main(String[] args) throws InterruptedException {
        //信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
        Semaphore semaphore = new Semaphore(20);
        //倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这1000个线程每个都报数了(也就是执行countDown方法)
        CountDownLatch countDownLatch = new CountDownLatch(200);
        for (int i = 0; i < 200; i++) {
            //!!!!重点来了,向我们的线程池提交一个Runnable的run方法,首先看线程池中有没有空闲的已创建的线程
            //如果有使用现成的线程, 如果没有则创建新的线程来执行
            fixedThreadPool.submit(() -> {
                try {
                    semaphore.acquire();//获取到信号量才可以进行加1操作
                    Thread.sleep(80);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
                    add();
                    semaphore.release();//执行完释放信号量
                    countDownLatch.countDown();//不要忘了报数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            int threadCount = ((ThreadPoolExecutor) fixedThreadPool).getActiveCount();
            long taskCount = ((ThreadPoolExecutor) fixedThreadPool).getTaskCount();
            System.out.println(MessageFormat.format("当前活跃线程数 :{0}", String.valueOf(threadCount)));
            System.out.println(MessageFormat.format("已完成任务 :{0}", String.valueOf(taskCount)));
        }
        countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码  自己可以注释掉试下
        System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
        fixedThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
    }

    /**
     * 加上synchronized关键字保证
     */
    public static synchronized void add() {
        count++;
    }

}
  1. Executors.newSingleThreadExecutor()
package allen.interview.thread.pool.sourceCodeLearn;

import java.text.MessageFormat;
import java.util.concurrent.*;

/**
 * java.util.concurrent.Executors 包下的 newSingleThreadExecutor 创建线程池使用的例子
 * 该例子场景描述: 简单 做1000次加一操作
 *
 */
public class SingleThreadPoolExample {
    /**
     * 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
     *  return new ThreadPoolExecutor(1, 1,
     *                                       0L, TimeUnit.MILLISECONDS,
     *                                       new LinkedBlockingQueue<Runnable>());
     * 核心线程数: 1
     * 最大线程数量:1
     * 线程最大空闲时间:0
     * 空闲时间单位:milliSeconds
     * 阻塞队列: LinkedBlockingQueue 一个不限制大小的队列(int 最大值的容量)
     */
    private static ExecutorService singleThreadPool=Executors.newSingleThreadExecutor();

    private static int count =0;

    public static void main(String[] args) throws InterruptedException {
        //信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
        Semaphore semaphore = new Semaphore(1);
        //倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这100个线程每个都报数了(也就是执行countDown方法)
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            //提交线程,只有一个线程在执行,所以,其他提交的任务都在队列中等待,线程去处理
            singleThreadPool.submit(() -> {
                try {
                    semaphore.acquire();//获取到信号量才可以进行加1操作
                    Thread.sleep(8);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
                    add();
                    semaphore.release();//执行完释放信号量
                    countDownLatch.countDown();//不要忘了报数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码  自己可以注释掉试下
        System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
        singleThreadPool.shutdown();//测试完成关闭资源,不然main方法会一直运行
    }

    /**
     * 加上synchronized关键字保证
     */
    public static synchronized void add() {
        count++;
    }

}
  1. Executors.newScheduledThreadPool(5)
package allen.interview.thread.pool.sourceCodeLearn;

import java.text.MessageFormat;
import java.util.concurrent.*;

/**
 * java.util.concurrent.Executors 包下的 newScheduledThreadPool 创建线程池使用的例子
 * 该例子场景描述: 简单 做1000次加一操作
 *
 */
public class ScheduledThreadPoolExample {
    /**
     * 首先贴出使用ThreadPoolExecutor构造函数创建的四个参数我的天啊
     *  return new ThreadPoolExecutor(coreNum, 1,
     *                                       0L, TimeUnit.MILLISECONDS,
     *                                       new LinkedBlockingQueue<Runnable>());
     * 核心线程数: coreNum
     * 最大线程数量:int 最大值
     * 线程最大空闲时间:0
     * 空闲时间单位:milliSeconds
     * 阻塞队列: DelayedWorkQueue
     */
    private static ScheduledExecutorService scheduledExecutorService=Executors.newScheduledThreadPool(5);
    private static int count =0;

    public static void main(String[] args) throws InterruptedException {
        //信号量控制最大并发量200,把信号量理解成车库门卫就可以了 总共两百个车位可以使用
        Semaphore semaphore = new Semaphore(1);
        //倒数阀门,类似于3 ,2, 1 芝麻开门的功能, 只有这100个线程每个都报数了(也就是执行countDown方法)
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {

            //提交线程,只有一个线程在执行,所以,其他提交的任务都在队列中等待,线程去处理
            scheduledExecutorService.schedule(() -> {
                try {
                    semaphore.acquire();//获取到信号量才可以进行加1操作
                    Thread.sleep(8);//模拟一个耗时操作,世界上最难的事是装作自己很忙的样子
                    add();
                    semaphore.release();//执行完释放信号量
                    countDownLatch.countDown();//不要忘了报数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },3,TimeUnit.SECONDS) ;

        }
        countDownLatch.await();//等到倒数数量执行为0,才会执行下边的代码  自己可以注释掉试下
        System.out.println(MessageFormat.format("count :{0}", String.valueOf(count)));
        scheduledExecutorService.shutdown();//测试完成关闭资源,不然main方法会一直运行
    }

    /**
     * 加上synchronized关键字保证
     */
    public static synchronized void add() {
        count++;
    }

}

阻塞队列BlockingQueue详解

重复一下新任务进入时线程池的执行策略:

如果运行的线程少于corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存入queue中,而是直接运行)
如果运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

其中使用到的BlockingQueue:

  • LinkedBlockingQueue
    该队列可以使有界队列,也可以是无界的(int最大值)
    newFixedThreadPool 使用的就是未指定的该队列,使用的时候需要注意,是无界的
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue同步移交队列
    如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

未使用但是常见的队列

  • ArrayBlockingQueue, 一个有界的队列
  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。