java学习笔记——线程池

1. 为什么有线程池?
我们知道,当程序接到一个任务后就会创建一个线程去执行这个任务。此时JVM会为线程分配内存并初始化成员变量。任务完成后就销毁。那么,来一个任务就创建一个线程,之后继续销毁?这样是不是太浪费资源了。线程可是非常宝贵的资源啊。所以我们可不可以在线程执行完后将其保存起来,等以后有相似的任务后,再run呢?就好比运沙车,没沙运的时候将车放入车库?所以线程池的概念就出来了。数据库连接池也是相应的道理。数据库连接也是宝贵的资源啊(_
2.线程池的原理
既然是一个存放线程的池子,那么,线程池的工作要务肯定就是控制管理运行的线程啦。既然要探讨原理,我们肯定得从源码说起了。我们先来介绍一下四个常见的线程池

  1. 四个基本线程池
    创建单个线程:Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

我们可以看出,Executors.newSingleThreadExecutor()返回的是一个只有一个线程的线程池,在线程死亡(发生异常)之后重新启动一个线程替代之前的继续工作下去。
创建缓存线程:Executors.newCachedThreadPool()

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

创建固定数量的线程池:Executors.newFixedThreadPool()

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

创建定期执行,定时线程:Executors.newScheduledThreadPool()

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

通过观察四个线程池的创建代码,我们可以发现,线程池的创建都是基于一个类:ThreadPoolExecutor,只是传入的参数各异。我们通过源码来看一下这个类是怎样创建线程池的。
其中一个构造函数

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:线程池核心线程数量
maximumPoolSize:线程池所允许的最大线程数量
keepAliveTime:指定了空闲线程的存活时间
unit:时间的单位(秒、毫秒)
workQueue:任务队列,等待线程执行
handler :线程池的拒绝策略(当线程数量到达允许的最大值时如何拒绝任务)
线程池工作过程
通过上面源码,我们可以大致看出线程池工作大致分为:创建线程,添加任务,运行线程执行任务。
1.我们可以看出,任务队列workQueue是作为一个参数传进去的,既,线程池刚刚创建的时候,是不会马上执行任务的。
2.线程池通过execute()方法来添加一个任务。通过下面源码分析,我们可以知道,添加一个任务后,线程池会进行以下判断
当当前线程数量<corePoolSize时,线程池会去创建一个线程来执行这个任务。
当线程数量>corePoolSize时,会将这个任务添加到workQueue中。
如果队列满了,但是<maximumPoolSize时,线程池还是会创建非核心线程来执行任务。
但当线程数量到达极限,队列也满的时候,就会抛出RejectExecutionException异常

final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

3.当一个线程执行完一个任务后,会去workQueue阻塞队列中获取下一个任务执行。
4.当线程的空闲时间keepAliveTime抵达之后,如果这时线程数量>corePoolSize,线程池会销毁这些空闲线程。
拒绝策略
当线程数量都在运行,且无法创建新线程的时候,线程池无法为新任务服务,阻塞队列也放不下了。这时,就要采用合理的拒绝策略来拒绝执行这些新任务。
JDK 内置的拒绝策略如下:

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。
  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
    以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。

猜你喜欢

转载自blog.csdn.net/weixin_43353645/article/details/89856692
今日推荐