线程池的创建和使用

什么事线程池?为什么要使用线程池?

线程池
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

我们详细的解释一下为什么要使用线程池?
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况

几种线程池的创建和使用

1. newFixedThreadPool固定线程池
示例:ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);

源码

/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

源码解读:
1).创建一个线程池,该线程池复用固定数量的线程去操作一个共享的无界队列;
2).在任何时刻,最多只有nThreads的线程是处于可处理任务的活跃状态。
3).当所有的线程都处于活跃状态(在处理任务),如果提交了额外的任务,它将会在队列中等待,直到有线程可用。
4).如果线程在执行期间由于失败而终止,如果需要的话,一个新的线程将会取代它执行后续任务。
5).线程池中的线程将会一直存在,直到显示的关闭。

这里需要注意,线程的数量是固定的,但是队列大小是无界的(Integer.MAX_VALUE足够大,大到可以任务无界。)由是于队列无界,处理复杂任务时,无界队列可能会让内存爆掉。
注意这里用的队列:LinkedBlockingQueue,默认队列大小Integer的最大值。

/**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

2. newSingleThreadExecutor单线程
示例: ExecutorService service = Executors.newSingleThreadExecutor();

源码

/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

源码解读:
创建一个线程执行者,它使用单个线程去操作一个无界队列。
(需要注意:如果一个线程由于执行过程中失败导致线程终止,一个新的线程将会取代他,如果需要执行后续任务,这里使用的队列,也是LinkedBlockingQueue,需要注意。)

3.newCachedThreadPool缓存线程池
示例:ExecutorService service1 = Executors.newCachedThreadPool();
源码:

/**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

源码解读:这里,使用的是异步队列SynchronousQueue,而且是非公平的。

4. ThreadPoolExecutor 这种方式创建线程池,参数很多,由于可以显示指定队列的大小,所以可以合理避免OOM;
示例:ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
4, //corePoolSize:线程核心数量,及时处于idle状态,也不会回收
10, //maximumPoolSize:线程数的上限
60, //keepAliveTime:超过这个时间,超过corePoolSize的线程,多余的线程将会被回收
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(300), //任务的排队队列 不显示指定大小,会默认 Integer.MAX_VALUE,设置不当容易OOM
new ThreadPoolExecutor.AbortPolicy() //拒绝策略
拒绝策略:

AbortPolicy:抛出RejectedExecutionException异常。这种策略时,注意捕获异常。
DiscardPolicy:什么也不做,直接忽略
DiscardOldestPolicy:丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
CallerRunsPolicy:直接由提交任务者执行这个任务

此种方式创建的线程各参数工作步骤如下:

1.刚开始,没有提交任务时,线程池是空的,队列也是空的;
2.提交一个任务;
3.此时,线程池中,因为没有线程,没有达到corePoolSize也就是4,所以新建一个线程来执行这个任务;
4.此时,如果这个任务处理完了,这个线程并不会被销毁,而是进入阻塞状态
5.由于使用的是LinkedBlockingQueue,如果不设置大小,那最大任务数量就是Integer.MAX_VALUE,一直添加任务可能会爆掉内存。
6.3中创建的线程会用阻塞的方式,从LinkedBlockingQueue这个任务队列里获取任务,如果没有任务一直阻塞,来任务了就去执行任务;
7.现在又来了一个任务,如果3中创建的线程还在处理别的任务,那么,此时,会看线程数量是否达到corePoolSize,如果没有,那就新建一个线程来处理;
8.重复以上,直到线程数量达到corePoolSize;
9.此时,如果再添加任务,而且corePoolSize中的线程都在处理任务中,那么,任务会直接压进队列中,直到队列满了;
10.此时,如果corePoolSize的线程都在处理任务,而且队列也满了,任务已经无法入队了;
11.此时,新来了任务,那么会创建新的线程,直到线程数量达到maximumPoolSize;
12.此时,如果maximumPoolSize中所有的线程都在处理任务,而且队列也满了,那新来了任务怎么办?
13.new ThreadPoolExecutor.AbortPolicy() 这个拒绝策略开始起作用了。
14.在上面,线程的数量已经达到了maximumPoolSize,超过corePoolSize,也是会一直阻塞等到获取队列中的任务,如果获取不到,空闲的时间超过了keepAliveTime,那么这个线程就销毁了。
15.可以看到,只有corePoolSize的线程都在处理任务,而且队列满了,才会去创建新的线程直到maximumPoolSize,keepAliveTime才会有作用,所以,如果不设置队列大小,他默认的大小是Integer.MAX_VALUE,那近乎无限的大小,这么多任务让corePoolSize来处理,那是处理不完的,任务会一直积压,内存爆掉。

综合上述方式,个人推荐第四种创建线程池的方式。

下面附上个人创建线程池工具方法:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

public class ThreadPoolExecutorUtil
{
    private static ExecutorService threadPool;
	private static ThreadFactory namedThreadFactory	= new ThreadFactoryBuilder().setNameFormat("UserThreadPool-%d").build(); // 此参数是给线程池命名

	public static synchronized ExecutorService getThreadPool()
	{
	    if (threadPool == null) 
	    {
	        threadPool = new ThreadPoolExecutor(5, 6, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
	    }
		return threadPool;
	}

	public static synchronized void shutdown(ExecutorService service)
	{
		if (service != null && !service.isShutdown())
		{
			service.shutdown();
		}
	}

}

在业务代码里面直接使用:

ExecutorService threadPool = ThreadPoolExecutorUtil.getThreadPool();
			threadPool.execute(() ->
			{
			...........业务代码.................
			}
			//使用完之后关闭线程池
			ThreadPoolExecutorUtil.shutdown(threadPool); 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值