线程池基础
什么是线程池
为什么使用线程池
使用线程池有哪些优势
线程池的使用
Java内置线程池
内置线程池是一个叫做ThreadPoolExecutor的一个类我们来看看它的构造方法
参数详解
流程图
参数设计
这里稍微用个人理解去解释一下,核心线程数说白了就是在80%的情况下能够处理系统任务的线程数量,任务队列长度的设计核心线程数量/单个任务执行时间*2 说白了就是单位时间内可以执行的任务数量2,其实对应的就是上面的80%情况下系统任务数量2
然后最大线程数量,看公式最大任务数量-任务队列长度这个说白了就是超出核心线程能够处理的范围的线程数量,然后*单个任务执行时间,就是要能够满足任务数量的线程数。
自定义线程池
下面我们思考并实现一个自己的线程池,那么一个线程池需要些什么东西呢?
首先线程池其实是在执行一些任务,所以我们需要有任务,然后线程池是存储了线程然后线程是用来执行任务的,所以肯定还需要有线程,最后我们肯定不能少了主角线程池
任务
任务既然是任务那么就肯定可以执行,所以我们应该让它可以被执行所以肯定需要实现Runnable接口,我们后续还需要判断是哪些任务执行了,所以最好再给一个编号给它。
package cn.cuit.thread;
public class MyTask implements Runnable{
// 编号
public int num;
public MyTask(int numb){
num = numb;
}
@Override
public void run() {
System.out.println("任务-" + num + "即将执行!");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务-" + num + "执行完毕!");
}
}
线程
我们现在需要一个线程去执行它,那么开启一个线程我们需要实现一个Thread接口才可以,然后为了后面方便看见效果依然给一个编号,并且它的任务就是不断地去执行任务队列中的各种队列,并且既然需要执行任务队列中的任务,那么必须要有任务队列
package cn.cuit.thread;
import java.util.List;
public class MyThread extends Thread{
// 任务队列
private List<MyTask> tasks;
// Id
String id;
public MyThread(String id, List<MyTask> tasks){
this.id = id;
this.tasks = tasks;
}
@Override
public void run() {
while(tasks.size() > 0) {
MyTask task = tasks.remove(0);
System.out.println("线程-" + id + "接受到任务" + task.num);
task.run();
}
}
}
线程池
我们的线程池首先需要有一些参数,我们之前说过的,核心线程数量,最大线程数量,队列长度
package cn.cuit.thread;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class MyThreadPool {
// 核心线程数量
int coreThreadNum;
// 最大线程数量
int maxThreadNum;
// 任务队列
List<MyTask> taskQueue = Collections.synchronizedList(new LinkedList());
// 最大任务数量
int maxTaskNum;
// 当前线程数量
int currentNum = 0;
// 提交任务
public void submitTask(MyTask task){
// 判断任务数量有没有达到上限
if ( taskQueue.size() < maxTaskNum) execTask(task);
else denyTaks(task);
}
// 构造方法
public MyThreadPool(int coreThreadNum, int maxThreadNum, int maxTaskNum) {
this.coreThreadNum = coreThreadNum;
this.maxThreadNum = maxThreadNum;
this.maxTaskNum = maxTaskNum;
}
/**
* 拒绝任务
* @param task
*/
private void denyTaks(MyTask task) {
System.out.println("任务-" + task + "已被拒绝...");
}
/**
* 执行任务
* @param task
*/
public void execTask(MyTask task) {
taskQueue.add(task);
// 还未达到核心线程数量,继续创建线程
if(coreThreadNum > currentNum) new MyThread("核心线程-" + (++currentNum),taskQueue).start();
// 达到核心线程,但是还没有达到最大线程数量
else if(maxThreadNum > currentNum) new MyThread("非核心线程-" + (++currentNum), taskQueue).start();
// 被缓存
else {
System.out.println("任务-" + task.num + "被缓存了");
}
}
}
测试
package cn.cuit.thread;
public class Test {
public static void main(String[] args){
// 创建线程池
MyThreadPool pool = new MyThreadPool(4, 8, 40);
// 这里我们核心线程数量是4个,200ms执行一个也就是一个线程1s可以执行5个,也就是平时状况下可以接受20个任务每秒,然后
// 我们最大线程是8,也就是我们最大可以接受40个任务每秒
for(int i = 0; i<50; i++) {
pool.submitTask(new MyTask(i));
}
}
}
我们最后来看结果
结果中有的被缓存了,有的被核心线程执行有的被非核心线程执行了,所以算是达到了基本效果了。
Java内置线程池
获取ExecutorService对象
获取ExecutorService并提交
package cn.cuit.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class Test {
// 任务类
static class MyTask implements Runnable{
String num;
public MyTask(String num) {
this.num = num;
}
@Override
public void run(){
System.out.println("线程-" + Thread.currentThread().getName() + "正在执行任务-" + num);
}
}
public static void main(String[] args){
/*测试newCachedThreadPool*/
// test1();
//test2();
/*测试固定的数量的线程*/
// test3();
//test4();
/*测试单线程的Executor*/
// test5();
test6();
}
public static void test6() {
ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
int i = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程" + i++);
}
});
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
public static void test5(){
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
public static void test4() {
ExecutorService es = Executors.newFixedThreadPool(3,new ThreadFactory() {
int i = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程" + i++);
}
});
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
public static void test3(){
ExecutorService es = Executors.newFixedThreadPool(3);
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
public static void test2() {
ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
int i = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程" + i++);
}
});
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
public static void test1(){
ExecutorService es = Executors.newCachedThreadPool();
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
}
}
我们来看一下运行结果
newCachedThreadPool
可以看到第一种我们的newCachedThreadPool里面用的不论有多少任务都会创建一个新的线程进行执行,然后传入了ThreadFactory我们确实可以对线程的创建起到一定的控制作用。可以按照自己的规则对线程进行创建
newFixedThreadPool
这两者我们可以看到我们给设置了固定的线程数量就是3个,确实也是三个线程再执行。
newSingleThreadPool
我们可以看见这两个都是只有一个线程再进行执行。
shutdown和shutdownNow
shutdown是关闭线程池,没有执行完毕的继续执行,不能继续添加新任务,shutdownNow关闭线程池,没有执行的也停止并返回
package cn.cuit.thread;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
// 任务类
static class MyTask implements Runnable{
String num;
public MyTask(String num) {
this.num = num;
}
@Override
public void run(){
System.out.println("线程-" + Thread.currentThread().getName() + "正在执行任务-" + num);
}
}
public static void main(String[] args){
/*shutdown*/
// test1();
/*shutdownNow*/
test2();
}
public static void test2(){
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
List<Runnable> tasks = es.shutdownNow();
System.out.println(tasks);
}
public static void test1(){
ExecutorService es = Executors.newSingleThreadExecutor();
for(int i = 0; i<10; i++) {
es.submit(new MyTask("" + i));
}
// 关闭线程池
es.shutdown();
// 尝试继续提交任务
es.submit(new MyTask("" + 888));
}
}
观察结果
可以看见第一个shutdown依然所有的任务都执行了,后面关闭之后再次添加任务抛出了异常
shutdownNow发现只有一个线程执行了,剩下的都被返回了回来
ScheduledExecutorService
获取方式
功能
我们写代码来试一试它的延迟和定期操作
```language
```language
延时操作
package cn.cuit.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test {
// 任务类
static class MyTask implements Runnable{
String num;
public MyTask(String num) {
this.num = num;
}
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
@Override
public void run(){
System.out.println(sdf.format(new Date()) + "线程-" + Thread.currentThread().getName() + "正在执行任务-" + num);
}
}
public static void main(String[] args){
test1();
}
// 延时任务
public static void test1(){
ScheduledExecutorService ss = Executors.newScheduledThreadPool(3);
ss.schedule(new MyTask("" + 1), 5, TimeUnit.SECONDS);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " --> over!");
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9JS9fs0-1648371076819)(1)]
异步计算结果
future对象的获取
我们可以看到啊,有三个都可以获取Future对象,但是思考一下最后两个他们传入的参数是Runnable对象,这个Runnable的run方法是没有返回值的,所以这种情况下是非常扯犊子的压根没有什么太大的意义,所以我们要想进行异步计算任务一般都用第一种Callable
写一个测试
package cn.cuit.thread;
import java.util.concurrent.*;
public class Test {
// 异步计算任务
static class CaculateTask implements Callable<Integer> {
int a, b;
public CaculateTask(int x, int y){a = x; b = y;}
@Override
public Integer call() throws Exception {
System.out.println("正在准备计算....");
Thread.sleep(2000);
System.out.println("计算完成.......");
return a + b;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Integer> res = es.submit(new CaculateTask(1, 2));
System.out.println(res.isCancelled());
System.out.println(res.isDone());
System.out.println("计算结果为:" + res.get());
System.out.println(res.isCancelled());
System.out.println(res.isDone());
}
}
看结果
欸!感觉来说的话应该是主线程立马就结束了然后异步计算结果是要等2s才对为什么计算完成之后才有最后两个输出false和true呢?这是因为我们一旦用了get之后主线程就会卡在get的地方一直等待,等待计算完毕之后才会继续往下执行。
那么我们可不可以不等那么久呢?比如我有一个实时的计算任务,马上就需要完成如果没有完成那么数据的价值就失去了意义了,所以我如果异步任务我只等1s你没有计算完成就算了,有没有这种操作呢?其实有我们只需要在get中添加参数等待时间就可以做到了
比如我把刚刚代码中的get()修改为get(1,TimeUnit.SECONED)
这样我就只需要等1s那么执行结果会变成什么样呢?
会抛出异常,因为计算任务2s才执行完毕但是现在你1s钟的时候就要去获取当然会出现问题,所以如果遇到需要这种情况外面套上一个try在catch中执行任务取消操作并且立马做出下一步任务这样就可以做到实时然后不要的就取消。