关于多线程:多线程线程池基本知识

5次阅读

共计 5070 个字符,预计需要花费 13 分钟才能阅读完成。

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

创立线程

后期筹备

本大节所有代码都是在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 更技术文章!!!

正文完
 0