一、线程池
线程池是一种常见的多线程编程技术,它可以在执行任务时复用已创建的多个线程,并且可以控制同时运行的线程数以避免资源占用过多的问题。下面是一个简单的Java示例代码,演示如何使用线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个容量为 5 的线程池
for (int i = 0; i < 10; i++) {
executorService.execute(new Task(i)); // 提交任务,由线程池中空闲的线程执行
}
executorService.shutdown(); // 调用任务完成,关闭线程池
}
static class Task implements Runnable {
private int taskNum;
public Task(int num) {
this.taskNum = num;
}
@Override
public void run() { // 线程池中的线程会调用该方法进行具体任务的执行
System.out.println("正在执行task " + taskNum);
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task " + taskNum + " 执行完毕");
}
}
}
上述代码定义了一个包含10个任务的线程池(容量为5),每个任务被分配到池中可用的空闲线程,当线程完成任务后,它会返回线程池以便其他任务使用。当所有任务完成时,调用executorService.shutdown()方法关闭线程池。
总之,在编写多线程应用程序时,利用线程池可以更有效地管理资源和提高系统性能。同时,由于在线程池中的线程具有重复使用性、复用性和恰当的个数等优点,因此建议在需要频繁创建并执行线程的情况下,尽可能使用线程池来实现。
一、线程池基础
1.1 什么是线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象
1.2 为什么使用线程池
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;
1.3 线程池有哪些优势
- 线程和任务分离,提升线程重用性
- 控制线程并发数量,降低服务器压力,统一管理所有线程
- 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么线程池就免去了T1和T3的时间
1.4 应用场景
应用场景介绍:
- 网购商品秒杀
- 云盘文件上传和下载
- 12306网上购票系统
只要有并发的地方、任何数量大或小、每个任务执行时间长活短都可以使用线程池
只不过在使用线程池的时候,注意一下设置合理的线程池大小即可
二、线程池使用
2.1 Java内置线程池 ThreadPoolExecutor
2.1.1 线程池的七个参数
2.1.1.1 int corePoolSize 核心线程数量
线程池中的核心线程数量指的是线程池在正常情况下需要保持的最小线程数量,当我们执行任务时线程线程数量没有达到核心线程数量,那就会新开线程。
当有任务提交到线程池时,线程池会先尝试通过核心线程来处理任务。只有当核心线程都被占用,并且任务队列已满时,线程池才会创建新的线程。因此,核心线程数量的大小可以直接影响线程池的性能和资源消耗。如果核心线程数量设置过大,可能会浪费系统资源;如果设置过小,可能会导致任务等待时间增加,进而降低系统的响应速度。在实际应用中,应根据具体的业务场景和系统负载情况来合理设置线程池的核心线程数量。
2.1.1.2 int maximumPoolSize 最大线程数
线程池中的最大线程数量指的是线程池允许创建的最大线程数,包括核心线程和非核心线程。
当任务队列已满,并且当前线程数小于最大线程数时,线程池会创建新的线程来处理任务。但是设置过大的最大线程数可能会带来一些负面影响,如增加系统资源消耗和降低系统的稳定性。因此,在合理使用线程池进行任务调度的前提下,需要根据业务场景和系统负载情况合理设置线程池的最大线程数量。
2.1.1.3 long keepAliveTime 最大空闲时间
也可以叫做存活时间。
当我们的一个线程不用的时候允许空闲,但是空闲到一定时间之后线程池也会回收线程。
线程池中的最大空闲时间指的是一个非核心线程在空闲状态下保持存活的最长时间。
当线程池中的非核心线程数量超过了核心线程数量时,超过核心线程数量的这些线程被称为“非核心线程”。
这些非核心线程在处理完任务后并不会立即销毁,而是处于等待下一次任务的空闲状态。线程池中的最大空闲时间设置了一个阈值,若一个非核心线程在空闲状态下超过了该时间限制,线程池就会将其销毁,以释放系统资源。
合理设置线程池的最大空闲时间可以有效避免因长时间运行的线程造成的资源浪费和系统负载过高的问题。但是如果将最大空闲时间设置得太短,则可能导致过多的线程被销毁和创建,反而会影响性能。正确地设置线程池的参数需要综合考虑业务场景和系统负载情况,并通过实验和观测对其进行调优
2.1.1.4 TimeUnit unit 时间单位
枚举类
2.1.1.5 BlockingQueue workQueue 任务队列
作用:暂存尚未执行的任务的数据结构(临时缓冲区)
当我们的线程数量达到核心线程数量后,如果再有任务提交到线程池里面,此时不会里面创建新线程,而是将任务添加到任务队列里面去,只有当任务队列加满后,按照我们设置的最大线程数来逐步创建线程(创建的线程一定不大于最大线程数)
2.1.1.6 ThreadFactory threadFactory 线程工厂
允许我们自己参与创建线程的过程
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
2.1.1.7 RejectedExecutionHandler handler 饱和处理机制
线程数到达核心线程数量,并且任务队列满了,线程数也到达最大线程数了, 也就是说线程池处于饱和状态了,已经无法再融入其他任务了,那这个时候我们就能给出一个饱和处理机制。
比如让那些任务等一会啊,抛弃一些任务啊
- 创建一个新的
ThreadPoolExecutor
与给定的初始参数和默认线程工厂和拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
- 创建一个新的
ThreadPoolExecutor
与给定的初始参数和默认线程工厂。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
- 创建一个新的
ThreadPoolExecutor
与给定的初始参数和默认拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
创建一个新
ThreadPoolExecutor
给定的初始参数。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
2.2 线程池工作流程介绍
三、自定义线线程池
3.1 参数设计分析
3.1.1 核心线程数量 corePoolSize
核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定
例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理
3.1.2 任务队列长度 workQueue
任务队列长度一般设计为:核心线程数/单个任务执行时间成×2即可
例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200
3.1.3 最大线程数 maximumPoolSize
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定
例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,
最大线程数=(最大任务数-任务队列长度)×单个任务执行时间 : 最大线程数=(1000-200)×0.1=80
3.1.4 最大空闲时间 KeepAliveTime
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值。
用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可。
3.2 自定义线程池实现步骤
3.2.1 编写任务类(MyTask) 实现Runnable接口
/**
* 要求:
* 自定义线程池练习,这是任务类,需要实现接口
* 包含任务编号,每一个任务执行时间设计为0.2秒
*/
public class MyTask implements Runnable {
private int id ; // 任务编号
// 由于run方法是重写接口中的方法,不可添加形式参数,但是我们可以通过构造方法添加
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程:"+name+"即将执行任务:"+id);
try {
Thread.sleep(200); //休眠200毫秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程:"+name+"完成了任务:"+id);
}
public MyTask(int id) {
this.id = id;
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
3.2.2 编写线程类(MyWork) 用于执行任务 需要持有所有任务
所有的任务用一个集合存起来
/**
* 需求:
* 编写一个线程类,需要去继承Thread,设计一个属性,用于保存线程的名字
* 设计一个集合,用于保存所有的任务
*/
public class MyWorker extends Thread{
private String name; //保存线程名字
private List<Runnable> tasks; //保存将来所有的任务
/**
* 判断集合中是否有任务,只要有任务,就一直执行
*/
@Override
public void run() {
while (tasks.size()>0){
// 将第一个任务移除出来
Runnable runnable = tasks.remove(0);
// 一直执行任务
runnable.run();
}
}
// 利用构造方法给成员变量赋值
public MyWorker(String name, List<Runnable> tasks) {
super(name);
// this.name = name;
this.tasks = tasks;
}
}
3.2.3 编写线程池类(MyThreadPool) 包含提交任务,执行任务能力
/**
* 自定义的线程池类
* 成员变量:
* 1. 任务队列 集合来表示即可(线程安全的集合)
* 2. 当前线程数量
* 3. 核心线程数量
* 4. 最大线程数
* 5. 任务队列长度
* 没有设置最大空闲时间
*
* 成员方法:
* 1.提交任务:将任务添加到集合中(如果没有超出任务队列的长度则可以加入到任务队列中)
* 2.执行任务: 判断当前线程的数量,决定创建核心线程(如果当前线程数量在核心线程数量之下)还是非核心线程(当前线程数量在核心线程数量与最大线程数量之间)
*/
public class MyThreadPool {
// 1. 任务队列 集合来表示即可(线程安全的集合)
// LinkedList集合是非线程安全的,我们可以使用集合工具类中方法将其转变为线程安全的
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
// 2. 当前线程数量,初始时是0
private int num;
// 3. 核心线程数量
private int corePoolSize;
// 4. 最大线程数
private int maxSize;
// 5. 任务队列长度
private int workSize;
// 1.提交任务:
public void submit(Runnable runnable){
// 判断当前集合中任务的数量,是否超出了最大任务数量
if (tasks.size()>=workSize){
// TODO 简陋的饱和机制处理
System.out.println("任务:"+runnable+"被丢弃了");
}else {
tasks.add(runnable);
// TODO 执行任务
execTask(runnable); //这个地方最好传入线程,不传入任务
}
}
// 2.执行任务:
private void execTask(Runnable runnable) {
// TODO 判断当前线程池中的线程总数量,是否超出了核心数
if(num <=corePoolSize){
MyWorker myWorker = new MyWorker("核心线程:"+num,tasks);
// Thread t = new Thread(myWorker);
// 启动线程
myWorker.start();
num++;
}else if (num < maxSize){
MyWorker myWorker = new MyWorker("非核心线程:"+num,tasks);
// Thread t = new Thread(myWorker);
// 启动线程
myWorker.start();
num++;
}else {
// 因为我们在execTask之前,已经将任务添加到tasks集合中了,所以说是被缓存了
System.out.println("任务:"+runnable+"被缓存了");
}
}
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
}
3.2.4 编写测试类 创建线程池对象,提交多个任务测试
/**
* 1.创建线程池对象
* 2.提交多个任务
*/
public class MyTest {
public static void main(String[] args) {
// 1.创建线程池对象
MyThreadPool pool = new MyThreadPool(2,4,20);
// 2.提交多个任务
for(int i=0;i<10;i++){
// 3. 创建任务对象,并提交给线程池
MyTask myTask = new MyTask(i);
pool.submit(myTask);
}
}
}
四、 Java内置线程池 - ExecutorService 介绍
ExecutorService 接口是Java内置的线程池接口。
接口是无法直接创建对象的。
4.1 常用方法
- void shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务,即无法向线程池中提交任务,但是之前提交的任务会继续执行
-
List shutdownNow ( )
停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表(使用返回值的)
-
Future submit(Callable task )
方法重载
执行带返回值的任务,返回一个Future对象.
-
Future<?> submit(Runnable task)
方法重载
执行 Runnable 任务,并返回一个表示该任务的 Future。
-
Future submit ( Runnable task,T result)
方法重载
执行 Runnable 任务,并返回一个表示该任务的 Future。
4.2 ExecutorService 获取
ExecutorService 是一个接口,无法直接创建,但是可以利用JDK中的Executors类(工厂类)中的静态方法来获取ExecutorService 对象
4.2.1 static ExecutorService newCachedThreadPool ( )
创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
此方法获取的线程池的最大空闲时间是60秒
这个方法对线程的数量不做限制的,有多少任务,就创建多少线程,即优先执行任务,效率优先
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest01 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable myRunnable = new MyRunnable(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable(int id) {
this.id = id;
}
}
4.2.2 static ExecutorService newCachedThreadPool (ThreadFactory threadFactory)
**指定线程的创建方式 **
线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行
ThreadFactory 也是一个接口,只不过允许程序员自己写实现类,在实现类内部创建线程对象 ,相当于程序员可以控制线程池中每一个线程对象的创建
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest01 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
// 创建的线程和任务r绑定在一起,就可以执行了
return new Thread(r,"自定义的线程名称"+n++);
}
});
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable myRunnable = new MyRunnable(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable(int id) {
this.id = id;
}
}
4.2.3 static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,可以规定线程数量
在创建线程池的时候指定线程池中线程的数量,降低服务器的压力。
当我们任务特别多,任务放到缓存中,不会创建更多的线程来执行任务
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest02 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象 此时线程池中最多有三个线程
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable02 myRunnable = new MyRunnable02(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable02 implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable02(int id) {
this.id = id;
}
}
pool-1-thread-1执行了任务…0
pool-1-thread-3执行了任务…2
pool-1-thread-2执行了任务…1
pool-1-thread-3执行了任务…4
pool-1-thread-1执行了任务…3
pool-1-thread-3执行了任务…6
pool-1-thread-2执行了任务…5
pool-1-thread-3执行了任务…8
pool-1-thread-1执行了任务…7
pool-1-thread-2执行了任务…9
4.2.4 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建
与上边那个方法而言,此方法可以控制线程的创建
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest02 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象 此时线程池中最多有三个线程
ExecutorService executorService = Executors.newFixedThreadPool(3, new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名称"+n++);
}
});
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable02 myRunnable = new MyRunnable02(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable02 implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable02(int id) {
this.id = id;
}
}
自定义线程名称3执行了任务…2
自定义线程名称1执行了任务…0
自定义线程名称2执行了任务…1
自定义线程名称3执行了任务…3
自定义线程名称2执行了任务…4
自定义线程名称3执行了任务…6
自定义线程名称2执行了任务…7
自定义线程名称1执行了任务…5
自定义线程名称2执行了任务…9
自定义线程名称3执行了任务…8
4.2.5 static ExecutorService newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式(任务缓存的时候不限制数量)来运行该线程。
此种方法,只追求安全,不考虑性能
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest03 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable03 myRunnable = new MyRunnable03(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable03 implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable03(int id) {
this.id = id;
}
}
pool-1-thread-1执行了任务…0
pool-1-thread-1执行了任务…1
pool-1-thread-1执行了任务…2
pool-1-thread-1执行了任务…3
pool-1-thread-1执行了任务…4
pool-1-thread-1执行了任务…5
pool-1-thread-1执行了任务…6
pool-1-thread-1执行了任务…7
pool-1-thread-1执行了任务…8
pool-1-thread-1执行了任务…9
4.2.6 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。
/**
* 练习Executors获取ExecutorService,然后调用方法提交任务
*/
public class MyTest03 {
public static void main(String[] args) {
// 1.使用工厂类获取线程池对象
ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名称"+n++);
}
});
// 2. 提交任务:
for (int i=0; i<10;i++){
MyRunnable03 myRunnable = new MyRunnable03(i);
executorService.submit(myRunnable);
}
}
}
/**
* 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
*/
class MyRunnable03 implements Runnable{
private int id;
@Override
public void run() {
// 获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务...."+id);
}
public MyRunnable03(int id) {
this.id = id;
}
}
自定义线程名称1执行了任务…0
自定义线程名称1执行了任务…1
自定义线程名称1执行了任务…2
自定义线程名称1执行了任务…3
自定义线程名称1执行了任务…4
自定义线程名称1执行了任务…5
自定义线程名称1执行了任务…6
自定义线程名称1执行了任务…7
自定义线程名称1执行了任务…8
自定义线程名称1执行了任务…9
五、Java内置线程池 - ScheduledExecutorService
ScheduledExecutorService 是 ExecutorService 的子接口
ScheduledExecutorService 具备了延迟运行或定期执行任务的能力
5.1 ScheduledExecutorService 获取
5.1.1 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务.(线程的数量是固定的)
/**
* 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
*/
public class ScheduledExecutorServiceDemo01 {
public static void main(String[] args) {
// 1.具备延迟执行任务的线程池对象 (线程池里面最多有三个)
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
// 2.创建多个任务对象,提交任务,每个任务延迟执行
// es.schedule(new MyRunnable(1),2, TimeUnit.SECONDS);
for (int i = 1; i < 10; i++) {
es.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
}
// main方法结束标志
System.out.println("over");
}
}
class MyRunnable implements Runnable {
private int id;
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "执行了任务编号" + id);
}
public MyRunnable(int id) {
this.id = id;
}
}
over大概输出2秒后,控制台才输出其他内容
over
pool-1-thread-1执行了任务编号1
pool-1-thread-2执行了任务编号2
pool-1-thread-3执行了任务编号3
pool-1-thread-3执行了任务编号6
pool-1-thread-3执行了任务编号7
pool-1-thread-2执行了任务编号5
pool-1-thread-1执行了任务编号4
pool-1-thread-2执行了任务编号9
pool-1-thread-3执行了任务编号8
5.1.2 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
可以指定线程工厂,从工厂里面得到线程对象
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;
5.1.3 static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行(单线程,但是允许延期运行、定期运行)
5.1.4 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
可以指定线程工厂,从工厂里面得到线程对象
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
*/
public class ScheduledExecutorServiceDemo02 {
public static void main(String[] args) {
// 1.具备延迟执行任务的线程池对象 (线程池里面最多有三个)
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
int num=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名"+num++);
}
});
// 2.创建多个任务对象,提交任务,每个任务延迟执行
es.scheduleWithFixedDelay(new MyRunnable02(1), 2, 2,TimeUnit.SECONDS);
// main方法结束标志
System.out.println("over");
}
}
class MyRunnable02 implements Runnable {
private int id;
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
// 模拟任务的执行时间比较长
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name + "执行了任务编号" + id);
}
public MyRunnable02(int id) {
this.id = id;
}
}
5.2 ScheduledExecutorService 常用方法
ScheduledExecutorService常用方法如下
5.2.1 ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。
5.2.2 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务.(线程的数量是固定的)
/**
* 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
*/
public class ScheduledExecutorServiceDemo01 {
public static void main(String[] args) {
// 1.具备延迟执行任务的线程池对象 (线程池里面最多有三个)
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
// 2.创建多个任务对象,提交任务,每个任务延迟执行
// es.schedule(new MyRunnable(1),2, TimeUnit.SECONDS);
for (int i = 1; i < 10; i++) {
es.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
}
// main方法结束标志
System.out.println("over");
}
}
class MyRunnable implements Runnable {
private int id;
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "执行了任务编号" + id);
}
public MyRunnable(int id) {
this.id = id;
}
}
over大概输出2秒后,控制台才输出其他内容
over
pool-1-thread-1执行了任务编号1
pool-1-thread-2执行了任务编号2
pool-1-thread-3执行了任务编号3
pool-1-thread-3执行了任务编号6
pool-1-thread-3执行了任务编号7
pool-1-thread-2执行了任务编号5
pool-1-thread-1执行了任务编号4
pool-1-thread-2执行了任务编号9
pool-1-thread-3执行了任务编号8
5.2.3 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后(先延迟initialDelay时间),每间隔period时间重复执行一次command。(第一次开始到第二次开始之间的时间差)
/**
* 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
*/
public class ScheduledExecutorServiceDemo01 {
public static void main(String[] args) {
// 1.具备延迟执行任务的线程池对象 (线程池里面最多有三个)
ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() {
int num=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名"+num++);
}
});
// 2.创建多个任务对象,提交任务,每个任务延迟执行
es.scheduleAtFixedRate(new MyRunnable(1), 2, 2,TimeUnit.SECONDS);
// main方法结束标志
System.out.println("over");
}
}
class MyRunnable implements Runnable {
private int id;
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
// 模拟任务的执行时间比较长
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name + "执行了任务编号" + id);
}
public MyRunnable(int id) {
this.id = id;
}
}
over
自定义线程名1执行了任务编号1
自定义线程名1执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1…
5.2.4 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。(第一次结束到下一次开始之间的时间差)
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
*/
public class ScheduledExecutorServiceDemo02 {
public static void main(String[] args) {
// 1.具备延迟执行任务的线程池对象 (线程池里面最多有三个)
ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
int num=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名"+num++);
}
});
// 2.创建多个任务对象,提交任务,每个任务延迟执行
es.scheduleWithFixedDelay(new MyRunnable02(1), 2, 2,TimeUnit.SECONDS);
// main方法结束标志
System.out.println("over");
}
}
class MyRunnable02 implements Runnable {
private int id;
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
// 模拟任务的执行时间比较长
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(name + "执行了任务编号" + id);
}
public MyRunnable02(int id) {
this.id = id;
}
}
六、 Future - 异步计算结果
刚刚在java内置线程池使用时,没有考虑线程计算的结果,但开发中有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future对象获取线程计算的结果。
在业务中用多线程计算某个任务,但是必须等待线程的执行结果才能往下进行(要使用带返回值的线程,否则怎么样也获取不到某些结果)。
6.1 常用方法
boolean cancel(boolean maylnterruptlfRunning)
试图取消对此任务的执行。(任务正在执行或者还没有执行的时候 )
V get ()
如有必要,等待计算完成,然后获取其结果
V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)
boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。
boolean isDone()
如果任务已完成,则返回 true。
6.2 Future演示
**
* 练习异步计算结果
*/
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 获取一个线程池对象
ExecutorService es = Executors.newCachedThreadPool();
// 2. 创建Callable类型的任务对象
Future<Integer> submit = es.submit(new MyCall(2, 5));
// 3. 判断任务是否已经完成
boolean done = submit.isDone();
System.out.println("第一次判断任务是否完成:"+done);
// 4.判断任务是否已经被取消
boolean cancelled = submit.isCancelled();
System.out.println("第一次判断任务是否取消:"+cancelled);
// 5.无限期等待结果, 直到完成为止
Integer integer = submit.get();
System.out.println("任务执行的结果是:"+integer);
done = submit.isDone();
System.out.println("第二次判断任务是否完成:"+done);
cancelled = submit.isCancelled();
System.out.println("第二次判断任务是否取消:"+cancelled);
// *************************************
}
}
class MyCall implements Callable<Integer>{
private int a;
private int b;
@Override
public Integer call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name+"准备开始....");
Thread.sleep(2000);
System.out.println(name+"计算完成....");
return a+b;
}
public MyCall(int a, int b) {
this.a = a;
this.b = b;
}
}
七、 综合案例
7.1 案例
案例介绍:
假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:既然商品总数量是10个,那么我们可以在创建线程池的时候初始化线程数是10个及以下,设计线程池最大数量为10个
2:当某个线程执行完任务之后,可以让其他秒杀的人继续使用该线程参与秒杀;
3:使用svnchronized控制线程安全防止出现错误数据
代码步骤:
1:编写任务类,主要是送出手机给秒杀成功的客户
2:编写主程序类创建20个任务(模拟20个客户);
3.创建线程池对象并接收20个任务开始执行任务文章来源:https://www.toymoban.com/news/detail-433931.html
7.2 代码
public class MyTest {
public static void main(String[] args) {
// 1. 创建一个线程池对象
// 核心线程3,最大线程数量5 ,最大空闲时间1分钟,任务队列里面最多缓存15个任务
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(15));
// 2.创建任务对象
for (int i = 1; i <= 20; i++) {
MyTask myTask = new MyTask("客户"+i);
pool.submit(myTask);
}
// 3.关闭线程池
pool.shutdown();
}
}
/**
* 任务类:
* 包含商品数量,客户名称,送手机的行为
*/
public class MyTask implements Runnable{
// 设计一个变量,用于表示商品的数量
private static int num=10;
// 客户名称
private String userName;
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在使用"+name+"参与秒杀任务......");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 使用字节码文件作为锁对象
synchronized (MyTask.class){
if (num>0){
System.out.println(userName+"正在使用"+name+"秒杀了"+ --num +"商品");
}else {
System.out.println(userName+"秒杀失败");
}
}
}
public MyTask(String userName) {
this.userName = userName;
}
}
客户2正在使用pool-1-thread-2参与秒杀任务…
客户20正在使用pool-1-thread-5参与秒杀任务…
客户3正在使用pool-1-thread-3参与秒杀任务…
客户19正在使用pool-1-thread-4参与秒杀任务…
客户1正在使用pool-1-thread-1参与秒杀任务…
客户3正在使用pool-1-thread-3秒杀了9商品
客户20正在使用pool-1-thread-5秒杀了8商品
客户19正在使用pool-1-thread-4秒杀了7商品
客户4正在使用pool-1-thread-5参与秒杀任务…
客户5正在使用pool-1-thread-4参与秒杀任务…
客户6正在使用pool-1-thread-3参与秒杀任务…
客户1正在使用pool-1-thread-1秒杀了6商品
客户2正在使用pool-1-thread-2秒杀了5商品
客户7正在使用pool-1-thread-1参与秒杀任务…
客户8正在使用pool-1-thread-2参与秒杀任务…
客户8正在使用pool-1-thread-2秒杀了4商品
客户7正在使用pool-1-thread-1秒杀了3商品
客户6正在使用pool-1-thread-3秒杀了2商品
客户5正在使用pool-1-thread-4秒杀了1商品
客户10正在使用pool-1-thread-1参与秒杀任务…
客户9正在使用pool-1-thread-2参与秒杀任务…
客户12正在使用pool-1-thread-4参与秒杀任务…
客户4正在使用pool-1-thread-5秒杀了0商品
客户11正在使用pool-1-thread-3参与秒杀任务…
客户13正在使用pool-1-thread-5参与秒杀任务…
客户11秒杀失败
客户10秒杀失败
客户14正在使用pool-1-thread-3参与秒杀任务…
客户15正在使用pool-1-thread-1参与秒杀任务…
客户9秒杀失败
客户12秒杀失败
客户16正在使用pool-1-thread-2参与秒杀任务…
客户17正在使用pool-1-thread-4参与秒杀任务…
客户13秒杀失败
客户18正在使用pool-1-thread-5参与秒杀任务…
客户17秒杀失败
客户15秒杀失败
客户16秒杀失败
客户14秒杀失败
客户18秒杀失败文章来源地址https://www.toymoban.com/news/detail-433931.html
到了这里,关于Java——线程池详细讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!