线程池的回忆

这周项目中出现了一个问题。具体描述为:
这个项目会出一个FreeMark报告,报告执行是多个sql组合查询的,这样会导致查询结果非常慢,报告中涉及到的表有80多张,所以请求接口去执行80多个sql效率是非常慢的,记录一下解决办法。将sql分组,以多线程的方式并发执行sql。
先来回忆一下创建线程的几种方式。
1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、使用Callable和Future创建线程
然后,通过查阅资料,首先回忆一下线程池。

	/**
	 * 
	 * @param corePoolSize(线程池核心线程大小)
	 * 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
	 * @param maximumPoolSize(线程池最大大小)
	 * 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
	 * @param keepAliveTime(线程存活保持时间)
	 * 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
	 * @param unit(keepAliveTime的计量单位)
	 * @param workQueue(任务队列)
	 * 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务,jdk中提供了四种工作队列:
	 * 1、ArrayBlockingQueue 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
	 * 2、LinkedBlockingQuene 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
	 * 3、SynchronousQuene 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
	 * 4、PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
	 * @param threadFactory(线程工厂)
	 * 用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
	 * @param handler(线程饱和策略)
	 * 当线程池和队列都满了,再加入新线程会执行此策略。jdk提供了4中拒绝策略。
	 * 1、CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
	 * 2、AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
	 * 3、DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
	 * 4、DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
	 */
 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

关于线程池参数介绍的博客,摘自:https://blog.csdn.net/ye17186/article/details/89467919
线程池的构造方法有四个,上述介绍的是参数最全的一个。
其余三个如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

这个构造方法里里面缺少ThreadFactoryRejectedExecutionHandler ,其中ThreadFactory为DefaultThreadFactory。RejectedExecutionHandler默认为AbortPolicy 策略。

第二个构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

这个构造方法为6个参数。其中RejectedExecutionHandleryi依然默认为AbortPolicy

第三个构造方法:

  public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

这个构造方法参数缺少线程工厂。默认值依然同上。

接下来,我们熟悉一下Java中Executors类中几种创建各类线程的方法。
1、newCachedThreadPool
如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。核心线程池大小为0,最大为Integer.MAX_VALUE,线程空闲存活时间是60秒。默认队列为SynchronousQuene 。

ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 1; i <= 5; i++) {
            cachedThreadPool.execute(() -> {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(index + "end");
        });
}

内部创建源码:

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

2.newFixedThreadPool
创建固定大小线程池核心线程数即为最大线程数,线程不会被回收,直到调用shutdown方法回收。核心线程池大小自定义。最大长度等于核心线程池大小。线程空闲时间为0,默认队列为LinkedBlockingQueue。

ExecutorService threadPool = Executors.newFixedThreadPool(2);
		for (int i = 1; i <= 2; i++) {
            fixedThreadPool.execute(() -> {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });
 }

内部创建源码:


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

3、newScheduledThreadPool
创建定时或周期性任务执行线程池,该线程池可用于定时或周期性任务的执行。核心线程池大小自定义。最大长度Integer.MAX_VALUE。线程空闲时间为0,默认队列为DelayedWorkQueue。

//定时代码:
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
    //创建线程池后10秒执行
    scheduledThreadPool.schedule(() -> {
       System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
}, 10, TimeUnit.SECONDS);


//周期执行代码:
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
//创建线程池后10秒执行 , 之后每5秒执行一次
scheduleThreadPoolAtFixedRate.scheduleAtFixedRate(() -> {
      System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
}, 10, 5, TimeUnit.SECONDS);

内部创建源码:

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

4、singleThreadExecutor
创建单线的线程池该线程池有且仅有一个线程执行任务。
内部创建源码:

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

5、newWorkStealingPool创建并行执行线程池
创建一个拥有多个任务队列(以便减少连接数)的线程池


		// 设置并行级别为2,即默认每时每刻只有2个线程同时执行
        ExecutorService threadPool = Executors.newWorkStealingPool(2);
        	for (int i = 1; i <= 4; i++) {
            	newWorkStealingPool.execute(() -> {
	                try {
	                    Thread.sleep(1000);
	                } catch (InterruptedException e) {
	                    e.printStackTrace();
	                }
            });
        }

内部创建源码:

public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

java内部创建的几个线程池就介绍到这里,接下来记录一下。文章开始提出的问题的解决办法的demo。

//当前定义了5个线程,所以线程计数器为5
final CountDownLatch latch = new CountDownLatch(5);
ExecutorService threadPool = Executors.newFixedThreadPool(5);
List<Future<String>> futureTaskList = new ArrayList<Future<String>>(5);
for (int i = 0; i < 5; i++) {
	futureTaskList.add(threadPool.submit(new Callable<String>() {
		@Override
		public String call() throws Exception {
			//将count值-1
			latch.countDown();
			return "ok...";
		}
	}));
}
	try {
		//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
		latch.await();
	} catch (InterruptedException e1) {
		e1.printStackTrace();
	}

	try {
		for (Future<String> future : futureTaskList) {
			System.out.println(future.get());
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	} catch (ExecutionException e) {
		e.printStackTrace();
	}

这段demo中用到一个类是CountDownLatch
CountDownLatch类似一个计数器,他的作用是等待其他线程执行各自执行完毕之后在执行。
他的原理是通过计数来实现。计数的初始值是线程的数量。每当一个线程执行完毕之后,我们将其减一。当计数器为0时,表示所有线程执行完毕。然后关闭的当前线程就可以执行工作了。
CountDownLatch中有三个重要的方法。

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  
//将count值减1
public void countDown() { };  

好了,就记录到这里吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值