上篇文章讲了下线程的创立及一些罕用的办法,然而在应用的时候,大多数是采纳了线程池来治理线程的创立,运行,销毁等过程。本篇将着重讲线程池的根底内容,包含通过线程池创立线程,线程池的根本信息等。

创立线程

后期筹备

本大节所有代码都是在CreateThreadByPool 类上,该类还有一个外部类MyThread 实现了Runnable 接口。

首先先把根本的代码给写进去

public class CreateThreadByPool {    public static void main(String[] args) {    }}class MyThread implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " processing");        process();        System.out.println(Thread.currentThread().getName() + " end");    }    private void process() {        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    @Override    public String toString() {        return String.format("MyThread{%s}", Thread.currentThread().getName());    }}

先来大略回顾一下,当咱们想创立10个线程的时候的代码一般形式是怎么的

private static void createThreadByNormalWay() {    for (int i = 0; i < 10; i++) {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread);        thread.start();    }}

能看到的代码中,是应用了start() 本人间接开启了线程,然而如果用线程池形式来呢

通过Executors

第一种创立线程池的办法是通过Executors 类的静态方法来构建,通过这种形式总共能够创立4种线程池

并且能够发现返回是ExecutorService ,所以还要承受返回值,最初通过execute 来启动线程

private static void createThreadByPool() {    ExecutorService executorService = Executors.newFixedThreadPool(5);    for (int i = 0; i < 10; i++) {        MyThread myThread = new MyThread();        executorService.execute(myThread);    }}

先不论底层是如何实现的,至多代码上是把线程交给了线程池来执行,这样可能保障线程可能对立治理。

简略的比喻就是前者是要你本人去找班长签到,后者是班长对立治理这整个班的签到。在main函数中调用看看一般办法和通过线程池创立的线程有什么区别

能够很显著的看到有以下几点区别

  • 线程的名字都不一样
  • 并且一般形式是创立了10个线程,而后者只是创立了5个线程(是由咱们本人设定的
  • 前者基本上是10个线程都是同时解决,后者是最多只能解决5个线程,须要等线程执行完有闲暇能力解决其它线程。

通过ThreadPoolExecutor

除了应用Executors.newFixedThreadPool() 创立线程池,还能够通过new ThreadPoolExecutor() ,这里可能有的小伙伴会迷糊了,怎么下面放回的类是ExecutorService ,当初返回的又是ThreadPoolExecutor ,其实两者是同一个货色。

能够看到ThreadExecutorPool 是继承了 AbstractExecutorService ,而后者是实现了ExecutorService 。通过该办法创立的线程池的代码如下

能够先这样运行体验下,至于说构造函数外面不同参数的含意,在前面的篇幅中会说到,到时候再返回来看即可。
private static void createThreadByThreadPoolExecutor() {    ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());    for (int i = 0; i < 10; i++) {        MyThread myThread = new MyThread();        executor.execute(myThread);    }}

看下运行后果

输入后果没啥好讲的,然而如果仔细的小伙伴在上一个gif就会发现,通过线程池来启动线程的形式,程序并没有退出,会始终运行。这是因为咱们没有shutdown 线程池。

两者区别

回过头来看看Executors.静态方法 这种办法来创立线程池的源码

能够看到其实更深一层还是应用了new ThreadPoolExecutor() ,只不过咱们本人能定制的构造函数的参数变得极其少,这时候必定有小伙伴疑难了,那为什么不间接都用new ThreadPoolExecutor() 呢?

《阿里java开发手册》 嵩山版明确规定了两点,一是线程资源必须通过线程池提供,不容许自行显式创立线程;二是线程池不容许应用Executors去创立,而是通过ThreadPoolExecutor的形式去创立。

着重看第二点强制通过ThreadPoolExecutor的形式来创立线程,起因在上面也有,来看看FixedThreadPool和SingleThreadPool的源码

其它的不论,能够看到两者调用构造函数中的队列都是LinkedBlockingQueue ,这个队列是无边界的,所以有了容许申请长度为Integer.MAX_VALUE ,会沉积大量的申请 ,从而导致OOM。

再来看看CachedThreadPool的源码

留神这里构造函数的第二个参数是线程池最大线程数,它设置成了Integer.MAX_VALUE ,这就可能会创立大量的线程,从而导致OOM。

线程池信息

ThreadPoolExecutor

下面也能够看到,创立线程池最重要也是最应该应用的办法的是new ThreadPoolExecutor() ,接下来把重点放在ThreadPoolExecutor这个类下面

这个是类中的所有的属性,接下来再看看构造函数

有4种,然而归根结底只有以下这一种构造函数,讲下这些参数的意义,而后大家就能够回头看下上一大节的例子。

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue<Runnable> workQueue,                          ThreadFactory threadFactory,                          RejectedExecutionHandler handler) {    //省略实现}
  • corePoolSize :外围线程数,大白话就是可能工作的线程数量
  • maximumPoolSize :最大线程数,就是这个线程池能包容线程的数量
  • keepAliveTime :存活工夫,当线程池中的线程数量大于外围线程数的时候,如果时候没有工作提交,外围线程池外的线程不会立刻被销毁,而是会期待,直到期待的工夫超过了这个字段才会被回收销毁
  • unit :存活工夫的单位
  • workQueue :工作队列,就是在线程开始被调用前,就是存在这个队列中
  • threadFactory :线程工厂,执行程序创立新线程时应用的工厂
  • handler :回绝策略,当达到线程边界和队列容量而采取的回绝策略

对于这个回绝策略,简略说下,有四种实现。

实现RejectedExecutionHandler 接口就能实现本人的回绝策略

监控线程

上面就来简略实现一个本人的回绝策略,并且来看下上述类中属性的信息

首先须要一个监控线程类

class MonitorThread implements Runnable {        //注入一个线程池    private ThreadPoolExecutor executor;    public MonitorThread(ThreadPoolExecutor executor) {        this.executor = executor;    }    private boolean monitor = true;    public void stopMonitor() {        monitor = false;    }    @Override    public void run() {        //监控始终运行,每3s输入一次状态        while (monitor) {            //次要逻辑是监控线程池的状态            System.out.println(                    String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s",                            this.executor.getPoolSize(),                            this.executor.getCorePoolSize(),                            this.executor.getActiveCount(),                            this.executor.getCompletedTaskCount(),                            this.executor.getTaskCount(),                            this.executor.isShutdown(),                            this.executor.isTerminated(),                            this.executor.getRejectedExecutionHandler()));            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

同时实现自定义的回绝策略

其实这还是没有对r解决,回绝了就回绝了,只是打印进去,然而并没有实质性地解决
class MyRejectedExecutionHandler implements RejectedExecutionHandler {    @Override    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {        System.out.println("task is rejected");    }}

接下来就是public类TheradPoolInfo留神工作线程采纳的是上一大节的MyThread

public class ThreadPoolInfo {    public static void main(String[] args) throws InterruptedException {        //新建了一个线程池,外围线程数是3,最大线程数是5,30s        //队列是ArrayBlockingQueue,并且大小边界是3,回绝策略自定义输入一句话        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler());                //开启监控线程        MonitorThread monitorThread = new MonitorThread(executor);        new Thread(monitorThread).start();                //开启工作线程        for (int i = 0; i < 10; i++) {            executor.execute(new MyThread());        }                //敞开线程池和监控线程        Thread.sleep(12000);        executor.shutdown();        Thread.sleep(3000);        monitorThread.stopMonitor();    }}

预期后果: 通过构造函数能够晓得,预期是有3个外围线程执行工作,会回绝2个线程,实现8个工作(最大线程数是5,队列长度是3,具体会在下一篇文章中讲)。

能够看到后果和预期的一样

创作不易,如果对你有帮忙,欢送点赞,珍藏和分享啦!

上面是集体公众号,有趣味的能够关注一下,说不定就是你的宝藏公众号哦,根本2,3天1更技术文章!!!