上篇文章讲了下线程的创立及一些罕用的办法,然而在应用的时候,大多数是采纳了线程池来治理线程的创立,运行,销毁等过程。本篇将着重讲线程池的根底内容,包含通过线程池创立线程,线程池的根本信息等。
创立线程
后期筹备
本大节所有代码都是在
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 更技术文章!!!