线程池:ThreadPoolExecutor,ExecutorService,Executors讲解

线程池

【理解】线程池基本概念

【理解】线程池工作原理

【掌握】自定义线程池

【应用】java内置线程池

【应用】使用java内置线程池完成综合案例

线程池基础

概念介绍

1.什么是线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们学过的实现Runnable或Callable接口的实例对象;

2.为什么使用线程池

使用线程池最大的原因就是可以根据系统的要求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;

3.线程池有哪些优势

  1. 线程和任务分离,提升线程的重要性;
  2. 控制线程并发数量,降低服务器压力,统一管理所有线程;
  3. 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;(节约了创建和销毁的时间)

4.线程池应用场景介绍

本人接触过:数据中心接消息里异步记录日志的时候就用到了,应为记录日志也是个单独入库操作。

其他:1.网购商品秒杀

           2.云盘文件上传和下载

           3.12306网上购票系统等

总之:只要有并发的地方、任务数量大或小、每个人物执行时间长或短的都可以使用线程池;只不过在使用线程池的时候,注意一下设置合理的线程池大小即可;

线程池使用

  • java内置线程池

java内置线程池原理剖析

我们要想自定义线程池,必须先了解线程池的工作原理,才能自己定义线程池;这里我们通过观察java中ThreadPoolExecutor的源码来学习线程池的原理;

ThreadPoolExecutor部分源码剖析
构造方法
    public ThreadPoolExecutor(int corePoolSize,//核心线程数量
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//最大空闲时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列
                              ThreadFactory threadFactory) {//线程工厂
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);//饱和处理机制
    }

ThreadPoolExecutor参数详解

我们可以通过下面的场景了解ThreadPoolExecutor中的各个参数

a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);

在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设改银行总共就2个窗口(核心线程数量是2);

紧接着在a,b客户都没有结束的情况下c客户来了,始于经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,并告知他:如果1、2号工作人员空出,c就可以前去办理业务;

此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给b客户办理业务;假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数就是最大线程数),于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒绝接待e客户;

最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1小时(最大空闲时间),经理就会让着部分空闲的员工下班(销毁线程)但是为了保证银行正常工作(有一个allowCore ThreadTimeout变量控制是否允许销毁,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);

ThreadPoolExecutor流程总结示意图

自定义线程池-参数设计分析

通过观察java中的内置线程池参数讲解和线程池工作流程总结,我们不难发现,要设计一个好的线程池,就必须合理的设置线程池的4个参数;那到底改如何合理的设计4个参数的值呢?我们一起往下看

4个参数的设计:

1:核心线程数(corePoolSize)

核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;

2:任务队列长度(workQueue)

任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;

3:最大线程数(maximumPoolSize)

最大线程的设计出了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定;例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务-任务队列长度)*单个任务执行时间;既:最大线程数=(1000-200)*0.1=80个;

4:最大空闲时间(keepAliveTime)

这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生的时间间隔合理设置一个即可;

 

小提示:

上面4个参数的设置只是一般的设计原则,并不是固定的,用户也可以根据实际情况灵活调整!

java内置线程池-ExecutorService介绍

ExecutorService接口是java内置的线程池接口,通过学习接口中的方法,可以快速的掌握java内置线程池的基本使用

常用方法:

void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

List<Runnable> shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

<T> Future<T> submit(Callable<T> task) 执行带返回值的任务,返回一个Future对象。

Future<?> submit(Runnable task) 执行Runnable 任务,并返回一个表示该任务的Future。

<T> Future<T> submit(Runnable task,T result) 执行Runnable任务,并返回一个表示该任务的Future。

package com.itheima.demo02;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 练习Executors获取ExecutorService,然后调用方法,提交任务;
 * 练习shutdown()和shutdownNow()方法;
 * @author songhaibo
 *
 */
public class shutdownORshutdownNowTest {
	public static void main(String[] args) {
		//test01();
		test02();
	}

	//练习newSingleThreadExecutor方法
	private static void test01() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newSingleThreadExecutor();//  创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。		//2:提交任务;
		//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable4(i));
		}
		//3:关闭线程池,仅仅是不再接受新的任务,以前的任务还回继续执行
		es.shutdown();
		//es.submit(new MyRunnable4(888));//不能在提交新的任务
	}

	//练习newSingleThreadExecutor方法
	private static void test02() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
			int n = 1;

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "自定义的线程名称" + n++);
			}
		});//创建一个使用单个worker线程的Executor,且线程池中的所有线程都使用ThreadFactory来创建。
			//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable4(i));
		}
		//3:立刻关闭线程池,如果线程池中还有缓存的任务,没有执行,则取消执行,并返回这些任务
		List<Runnable> list = es.shutdownNow();
		System.out.println(list);
		//es.submit(new MyRunnable4(88));//不能在提交新任务

	}
}

/**
 * 任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
 * @author songhaibo
 *
 */
class MyRunnable4 implements Runnable {
	private int id;

	public MyRunnable4(int id) {
		super();
		this.id = id;
	}

	@Override
	public String toString() {
		return "MyRunnable4 [id=" + id + "]";
	}

	@Override
	public void run() {
		//获取线程的名称,打印一句话
		String name = Thread.currentThread().getName();
		System.out.println(name + "执行了任务。。。" + id);
	}

}

补充:shutdown()和submitNow()的区别?submit()和execute()有什么区别?

答:shutdown()是执行完之前的任务在关闭,shutdown()是直接停止所有任务。 submit()有返回值,execute()没有返回值。

 

思考:

既然ExecutorService是一个接口,接口是无法直接创建对象的,那么我们该如何获取ExecutorService的对象呢?

java内置线程池-ExecutorService获取

获取ExecutorService可以利用JDK中的Executors类中的静态方法,常用获取方式如下:

 

static ExecutorService newCachedThreadPool() 创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建

static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行;

static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池

static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。

static ExecutorService newSingleThreadExecutor() 创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。

static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 创建一个使用单个worker线程的Executor,且线程池中的所有线程都使用ThreadFactory来创建。

以下练习这三种获取线程池对象的方法

package com.itheima.demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 练习Executors获取ExecutorService,然后调用方法,提交任务;
 * @author songhaibo
 *
 */
public class newCachedThreadPoolTest {
	public static void main(String[] args) {
		//test01();
		test02();
	}

	//练习newCachedTheradPool方法
	private static void test01() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newCachedThreadPool();//创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
		//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable(i));
		}
	}

	//练习newCachedTheradPool方法
	private static void test02() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
			int n = 1;

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "自定义的线程名称" + n++);
			}
		});//线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行;
			//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable(i));
		}
	}
}

/**
 * 任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
 * @author songhaibo
 *
 */
class MyRunnable implements Runnable {
	private int id;

	public MyRunnable(int id) {
		super();
		this.id = id;
	}

	@Override
	public void run() {
		//获取线程的名称,打印一句话
		String name = Thread.currentThread().getName();
		System.out.println(name + "执行了任务。。。" + id);
	}

}

 

package com.itheima.demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 练习Executors获取ExecutorService,然后调用方法,提交任务;
 * @author songhaibo
 *
 */
public class newFixedThreadPoolTest {
	public static void main(String[] args) {
		//test01();
		test02();
	}

	//练习newFixedThreadPool方法
	private static void test01() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newFixedThreadPool(3);// 创建一个可重用固定线程数的线程池
		//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable2(i));
		}
	}

	//练习newFixedThreadPool方法
	private static void test02() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newFixedThreadPool(3, new ThreadFactory() {
			int n = 1;

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "自定义的线程名称" + n++);
			}
		});//创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。
			//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable2(i));
		}
	}
}

/**
 * 任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
 * @author songhaibo
 *
 */
class MyRunnable2 implements Runnable {
	private int id;

	public MyRunnable2(int id) {
		super();
		this.id = id;
	}

	@Override
	public void run() {
		//获取线程的名称,打印一句话
		String name = Thread.currentThread().getName();
		System.out.println(name + "执行了任务。。。" + id);
	}

}

 

package com.itheima.demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * 练习Executors获取ExecutorService,然后调用方法,提交任务;
 * @author songhaibo
 *
 */
public class newSingleThreadExecutorTest {
	public static void main(String[] args) {
		//test01();
		test02();
	}

	//练习newSingleThreadExecutor方法
	private static void test01() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newSingleThreadExecutor();//  创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。		//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable3(i));
		}
	}

	//练习newSingleThreadExecutor方法
	private static void test02() {
		//1:使用工厂类获取线程池对象
		ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
			int n = 1;

			@Override
			public Thread newThread(Runnable r) {
				return new Thread(r, "自定义的线程名称" + n++);
			}
		});//创建一个使用单个worker线程的Executor,且线程池中的所有线程都使用ThreadFactory来创建。
			//2:提交任务;
		for (int i = 0; i < 10; i++) {
			es.submit(new MyRunnable3(i));
		}
	}
}

/**
 * 任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
 * @author songhaibo
 *
 */
class MyRunnable3 implements Runnable {
	private int id;

	public MyRunnable3(int id) {
		super();
		this.id = id;
	}

	@Override
	public void run() {
		//获取线程的名称,打印一句话
		String name = Thread.currentThread().getName();
		System.out.println(name + "执行了任务。。。" + id);
	}

}

总结:

不考虑服务器性能及硬件压力可以用newCachedThreadPool()方法,因为他没有线程数量上限;

考虑虑服务器性能及硬件压力可以用newFixedThreadPool()方法,他可以设置线程数量;

考虑安全性的话可以用newSingleThreadExecutor();方法

  • 自定义线程池

编写任务类(MyTask),实现Runnable接口;

package com.itheima.demo01;

/**
 * 需求:自定义线程池练习,这是任务类,需要实现Runnable接口;
 * 包含任务编号,每一个任务执行时间设计为0.2秒
 * @author songhaibo
 *
 */
public class MyTask implements Runnable {
	private int id;

	//由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
	public MyTask(int id) {
		super();
		this.id = id;
	}

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		System.out.println("线程:" + name + "即将执行任务" + id);
		try {
			Thread.sleep(200);//模拟任务执行时间0.2秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程:" + name + "完成任务" + id);

	}

}

编写线程类(MyWorker),用于执行任务,需要持有所有任务;

package com.itheima.demo01;

import java.util.List;

/**
 * 需求:编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
 * 设计一个集合,用于保存所有的任务;
 * @author songhaibo
 *
 */
public class MyWorker extends Thread {
	private String name;//保存线程的名字
	private List<Runnable> tasks;

	//利用构造方法给成员变量赋值
	public MyWorker(String name, List<Runnable> tasks) {
		super();
		this.name = name;
		this.tasks = tasks;
	}

	@Override
	public void run() {
		//判断集合中是否有任务,只要有,就一直执行任务
		while (tasks.size() > 0) {
			Runnable r = tasks.remove(0);
			r.run();
		}
	}
}

编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;

package com.itheima.demo01;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 这是自定义的线程池类;
 * @author songhaibo
 	*成员变量:
 		*1:任务队列  集合 需要控制线程安全问题
 		*2:当前线程数量
 		*3:核心线程数
 		*4:最大线程数
 		*5:任务队列长度
 	*成员方法
 		*1:提交任务;
 				*将任务添加到集合中,需要判断是否超出了任务总长度
 		 *2:执行任务;
 		 		*判断当前线程的数量,决定创建核心线程还是非核心线程
 	*
 */
public class MyThreadPool {
	//1:任务队列  集合 需要控制线程安全问题
	private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<Runnable>());
	//2:当前线程数量
	private int num;
	//3:核心线程数
	private int corePoolSize;
	//4:最大线程数
	private int maxSize;
	//5:任务队列长度
	private int workSize;

	//1:提交任务;
	public void submit(Runnable r) {
		//判断当前集合中任务的数量是否超出了最大线程数
		if (tasks.size() >= workSize) {
			System.out.println("任务:" + r + "被丢弃了");
		} else {
			tasks.add(r);
			//执行任务
			execTask(r);
		}
	}

	//2:执行任务;
	private void execTask(Runnable r) {
		//判断当前线程池中的线程总数量,是否超出了核心数量
		if (num < corePoolSize) {
			new MyWorker("核心线程:" + num, tasks).start();
			num++;
		} else if (num < maxSize) {
			new MyWorker("非核心线程:" + num, tasks).start();
			num++;
		} else {
			System.out.println("任务:" + r + "被缓存了! 需要等待其他现场执行完之后在执行");
		}
	}

	public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
		super();
		this.corePoolSize = corePoolSize;
		this.maxSize = maxSize;
		this.workSize = workSize;
	}

}

编写测试类(MyTest),创建线程池对象,提交多个任务测试;

package com.itheima.demo01;

/**
 * 需求:自定义线程池练习,这是任务类,需要实现Runnable接口;
 * 包含任务编号,每一个任务执行时间设计为0.2秒
 * @author songhaibo
 *
 */
public class MyTask implements Runnable {
	private int id;

	//由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
	public MyTask(int id) {
		super();
		this.id = id;
	}

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		System.out.println("线程:" + name + "即将执行任务" + id);
		try {
			Thread.sleep(200);//模拟任务执行时间0.2秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程:" + name + "完成任务" + id);

	}

	@Override
	public String toString() {
		return "MyTask [id=" + id + "]";
	}

}

 

  • 异步计算结果(Future)

我们刚刚在学习java内置线程池使用时,没有考虑线程计算的结果,但开发中,我们有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future对象获取线程计算的结果;Future的常用方法如下:

boolean cancel(boolean mayInterruptlfRunning) 试图取消对次任务的执行

V get() 如有必要,等待计算完成,然后获取其结果。

V get(long timeout,TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)

boolean isCancelled() 如果在任务正常完成前将其取消,则返回true

boolean isDone() 如果任务已完成,则返回true。

线程池总结

线程池的使用步骤可以归纳总结为五步:

  1. 利用Executors工厂类的静态方法,创建线程池对象;
  2. 编写Runnable或Callable实现类的实例对象;
  3. 利用ExecutorService的submit方法提交任务;
  4. 如果有执行结果,则处理异步执行结果(Future);
  5. 调用shutdown()方法,关闭线程池;

 

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
四种线程池ThreadPoolExecutor有着密切的关系。在Java中,通过Executors提供了四种线程池,这四种线程池底层都是由ThreadPoolExecutor实现的。ThreadPoolExecutor是一个灵活的线程池类,可以根据需要进行配置和定制。通过掌握ThreadPoolExecutor的参数,我们可以不局限于四种线程池,而是根据实际需求构建自己的线程池。 其中,四种线程池分别是: 1. FixedThreadPool:固定大小的线程池,线程数固定,任务被放入一个无界队列中,适用于长期执行的任务。 2. CachedThreadPool:可缓存的线程池,线程数根据需求自动增加和减少,适用于执行大量短期任务的场景。 3. SingleThreadPool:单线程的线程池,只有一个线程在工作,适用于需要保证顺序执行的任务。 4. ScheduledThreadPool:定时任务的线程池,可以设置任务的定时执行时间。 ThreadPoolExecutor提供了更加灵活的线程池管理,可以通过设置不同的参数来控制线程池的行为。例如,可以选择不同的队列策略,如无界队列LinkedBlockingQueue,来控制线程池中任务的排队方式。通过学习和使用ThreadPoolExecutor,我们可以更好地理解和使用四种线程池,同时也可以根据实际需求进行自定义的线程池构建。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [JAVA线程池ThreadPoolExecutor实现的四种线程池](https://blog.csdn.net/a78270528/article/details/79881850)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Java ExecutorService四种线程池ThreadPoolExecutor机制](https://blog.csdn.net/Zzz_Zzz_Z/article/details/81218574)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值