目录
7.5丶SingleThreadScheduledExecutor
8.6丶Java内置线程池-ExecutorService介绍
8.7丶Java内置线程池-ExecutorService获取
8.10丶newSingleThreadExecutor练习
8.11丶练习Executors获取ExecutorService,测试关闭线程池的方法
8.12丶Java内置线程池-ScheduledExecutorService
8.13丶ScheduledExecutorService常用方法如下
8.14丶newScheduledThreadPool的schedule
一丶什么是线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
二丶为什么使用线程池
线程池是运用场景最多的并发框架,几乎所有需要一步或者并发执行任务的程序都可以使用线程池。使用线程池一般有以下三个好处:
- 降低资源的消耗,通过重复利用已经创建的线程降低线程创建和销毁造成的消耗。
- 提高相应速度,当任务到达的时候,任务可以不需要等到线程创建就能立刻执行。
- 提高线程的可管理性,线程是稀缺资源,使用线程池可以统一的分配、调优和监控。
三丶使用线程池有哪些优势
- 线程和任务分离,提升线程重用性;
- 控制线程并发数量,降低服务器压力,统一管理所有线程;
- 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
四丶线程池应用场景介绍
应用场景介绍
- 网购商品秒杀
- 云盘文件上传和下载
- 12306网上购票系统等
总之
只要有并发的地方、任务数量大或小、每个任务执行时间长或短的都可以使用线程池;
只不过在使用线程池的时候,注意一下设置合理的线程池大小即可;
- java内置线程池
- 自定义线程池
- 异步计算结果(Future)
五丶线程池的实现原理
当线程池提交一个任务到线程池后,执行流程如下:
线程池先判断核心线程池里面的线程是否都在执行任务。如果不是都在执行任务,则创建一个新的工作线程来执行任务。如果核心线程池中的线程都在执行任务,则判断工作队列是否已满。如果工作队列没有满,则将新提交的任务存储到这个工作队列中,如果工作队列满了,线程池则判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理 ,也就是拒接策略。
具体:
- 如果当前线程少于corePoolSize,就创建新的线程来执行任务,但是这一步会获取全局锁。
- 如果当前运行的线程大于等于corePoolSize,则将任务加入BlockingQueue。
- 如果无法将任务加入队列中, 队列已满的话,则创建新的线程处理任务。这一步需要全局锁。
- 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法。
执行execute()方法时,尽可能 地避免获取全局锁。
六丶 线程池参数介绍
- corePoolSize(核心线程数):当提交一个任务到线程池时,线程池会创建一个线 程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任 务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法, 线程池会提前创建并启动所有基本线程。
- workQueue(任务队列长度):用于保存等待执行的任务的阻塞队列。当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。可以选择以下几 个阻塞队列。
- ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
- LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
- SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
- PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
- DelayQueue 延时队列:无界,元素有过期时间,过期的元素才能被取出。
- maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并 且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如 果使用了无界的任务队列这个参数就没什么效果。
- ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设 置更有意义的名字。使用开源框架guava提供的ThreadFactoryBuilder可以快速给线程池里的线 程设置有意义的名字。
- RejectedExecutionHandler(拒绝策略):当队列和线程池都满了,说明线程池处于饱和状 态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法 处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略。
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以, 如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。 ·TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟 (MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒 (NANOSECONDS,千分之一微秒)
-
unit:时间单位:keepAliveTime的时间单位
-
TimeUnit.NANOSECONDS
-
TimeUnit.MICROSECONDS
-
TimeUnit.MILLISECONDS // 毫秒
-
TimeUnit.SECONDS
-
TimeUnit.MINUTES
-
TimeUnit.HOURS
-
TimeUnit.DAYS
-
七丶Java 中线程池的 7 种创建方式
在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:
- 通过 ThreadPoolExecutor 手动创建线程池。
- 通过 Executors 执行器自动创建线程池。
而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:
- Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
- Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
- Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
- Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
- Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
- ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。
7.1丶FixedThreadPool
创建一个固定大小的线程池,可控制并发线程数。
使用 FixedThreadPool 创建 2 个固定大小的线程池,具体实现代码如下:
public static void fixedThreadPool() {
// 创建 2 个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
}
};
// 线程池执行任务(一次添加 4 个任务)
// 执行任务的方法有两种:submit 和 execute
threadPool.submit(runnable); // 执行方式 1:submit
threadPool.execute(runnable); // 执行方式 2:execute
threadPool.execute(runnable);
threadPool.execute(runnable);
}
以上程序的执行结果如下图所示:
如果觉得以上方法比较繁琐,还用使用以下简单的方式来实现线程池的创建和使用:
public static void fixedThreadPool() {
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 执行任务
threadPool.execute(() -> {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
});
}
使用场景:
适用于任务数量比较固定,并且执行时间比较长的任务
7.2丶CachedThreadPool
创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。
CachedThreadPool 使用示例如下:
public static void cachedThreadPool() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 执行任务
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
});
}
}
以上程序的执行结果如下图所示:
从上述结果可以看出,线程池创建了 10 个线程来执行相应的任务。
使用场景:
CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。
一个可以无限扩大的线程池,适用于任务量比较大,但是任务执行时间很短,60s以内,不会造成CPU的过渡切换。
7.3丶SingleThreadExecutor
创建单个线程的线程池,它可以保证先进先出的执行顺序。
SingleThreadExecutor 使用示例如下:
public static void singleThreadExecutor() {
// 创建线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + ":任务被执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
});
}
}
以上程序的执行结果如下图所示:
单个线程的线程池有什么意义?
单个线程的线程池相比于线程来说,它的优点有以下 2 个:
- 可以复用线程:即使是单个线程池,也可以复用线程。
- 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。
使用场景:
适用于需要保证顺序执行的任务
7.4丶ScheduledThreadPool
创建一个可以执行延迟任务的线程池。
使用示例如下:
public static void scheduledThreadPool() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
// 添加定时执行任务(1s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(() -> {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 1, TimeUnit.SECONDS);
}
以上程序的执行结果如下图所示:
从上述结果可以看出,任务在 1 秒之后被执行了,实现了延迟 1s 再执行任务。
使用场景:
适用于执行延迟或者周期性的任务
7.5丶SingleThreadScheduledExecutor
创建一个单线程的可以执行延迟任务的线程池,此线程池可以看作是 ScheduledThreadPool 的单线程池版本。
它的使用示例如下:
public static void SingleThreadScheduledExecutor() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 添加定时执行任务(2s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(() -> {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 2, TimeUnit.SECONDS);
}
以上程序的执行结果如下图所示:
从上述结果可以看出,任务在 2 秒之后被执行了。
7.6丶newWorkStealingPool
创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。
newWorkStealingPool 使用示例如下:
public static void workStealingPool() {
// 创建线程池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
});
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}
}
以上程序的执行结果如下图所示:
7.7丶ThreadPoolExecutor
ThreadPoolExecutor 是最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。
ThreadPoolExecutor 使用示例如下:
public static void myThreadPoolExecutor() {
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
以上程序的执行结果如下图所示:
ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,所以在阿里巴巴《Java开发手册》是这样规定的:
【强制要求】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
7.8丶总结
线程池的创建方式总共有以下 7 种:
- Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
- Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
- Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
- Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
- Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
- ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。
而线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险。
8丶线程池练习
8.1丶自定义线程池-实现步骤
- 编写任务类(MyTask),实现Runnable接口;
- 编写线程类(MyWorker),用于执行任务,需要持有所有任务;
- 编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
- 编写测试类(MyTest),创建线程池对象,提交多个任务测试;
8.2丶MyTask
package com.itheima.demo01;
/*
需求:
自定义线程池练习,这是任务类,需要实现Runnable;
包含任务编号,每一个任务执行时间设计为0.2秒
*/
public class MyTask implements Runnable{
private int id;
//由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
public MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程:"+name+" 即将执行任务:"+id);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+" 完成了任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
8.3丶MyWorker
package com.itheima.demo01;
import java.util.List;
/*
需求:
编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
设计一个集合,用于保存所有的任务;
*/
public class MyWorker extends Thread{
private String name;//保存线程的名字
private List<Runnable> tasks;
//利用构造方法,给成员变量赋值
public MyWorker(String name, List<Runnable> tasks) {
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有,就一直执行任务
while (tasks.size()>0){
Runnable r = tasks.remove(0);
r.run();
}
}
}
8.4丶MyThreadPool
package com.itheima.demo01;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/*
这是自定义的线程池类;
成员变量:
1:任务队列 集合 需要控制线程安全问题
2:当前线程数量
3:核心线程数量
4:最大线程数量
5:任务队列的长度
成员方法
1:提交任务;
将任务添加到集合中,需要判断是否超出了任务总长度
2:执行任务;
判断当前线程的数量,决定创建核心线程还是非核心线程
*/
public class MyThreadPool {
// 1:任务队列 集合 需要控制线程安全问题
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
//2:当前线程数量
private int num;
//3:核心线程数量
private int corePoolSize;
//4:最大线程数量
private int maxSize;
//5:任务队列的长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = 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+" 被缓存了...");
}
}
}
8.5丶MyTest
package com.itheima.demo01;
/*
测试类:
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 <30 ; i++) {
//3:创建任务对象,并提交给线程池
MyTask my = new MyTask(i);
pool.submit(my);
}
}
}
8.6丶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。
8.7丶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来创建。
8.8丶newCachedThreadPool练习
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest01 {
public static void main(String[] args) {
// test1();
test2();
}
//练习newCachedThreadPool方法
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
8.9丶newFixedThreadPool练习
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest02 {
public static void main(String[] args) {
//test1();
test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newFixedThreadPool(3);
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable2(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newFixedThreadPool(3,new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable2(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable2 implements Runnable{
private int id;
public MyRunnable2(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
8.10丶newSingleThreadExecutor练习
package com.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,然后调用方法,提交任务;
*/
public class MyTest03 {
public static void main(String[] args) {
//test1();
test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable3(i));
}
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable3(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable3 implements Runnable{
private int id;
public MyRunnable3(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
8.11丶练习Executors获取ExecutorService,测试关闭线程池的方法
package com.test;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/*
练习Executors获取ExecutorService,测试关闭线程池的方法;
*/
public class MyTest04 {
public static void main(String[] args) {
test1();
// test2();
}
//练习方法newFixedThreadPool
private static void test1() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor();
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable4(i));
}
//3:关闭线程池,仅仅是不再接受新的任务,以前的任务还会继续执行
es.shutdown();
//es.submit(new MyRunnable4(888));//不能再提交新的任务了
}
private static void test2() {
//1:使用工厂类获取线程池对象
ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFactory() {
int n=1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义的线程名称"+n++);
}
});
//2:提交任务;
for (int i = 1; i <=10 ; i++) {
es.submit(new MyRunnable4(i));
}
//3:立刻关闭线程池,如果线程池中还有缓存的任务,没有执行,则取消执行,并返回这些任务
List<Runnable> list = es.shutdownNow();
System.out.println(list);
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable4 implements Runnable{
private int id;
public MyRunnable4(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
@Override
public String toString() {
return "MyRunnable4{" +
"id=" + id +
'}';
}
}
8.12丶Java内置线程池-ScheduledExecutorService
ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力
常用获取方式如下:
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务;
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
8.13丶ScheduledExecutorService常用方法如下
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
8.14丶newScheduledThreadPool的schedule
package com.test.demo3;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
测试ScheduleExecutorService接口中延迟执行任务和重复执行任务的功能
*/
public class ScheduleExecutorServiceDemo01 {
public static void main(String[] args) {
//1:获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
for (int i=1;i<=10;i++){
es.schedule(new MyRunnable(i),2, TimeUnit.SECONDS);
}
System.out.println("over");
}
}
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+id);
}
}
8.15丶scheduleAtFixedRate方法
package com.test.demo3;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/*
测试ScheduleExecutorService接口中延迟执行任务和重复执行任务的功能
*/
public class ScheduleExecutorServiceDemo02 {
public static void main(String[] args) {
//1:获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() {
int n = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"自定义线程名:"+n++);
}
});
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
es.scheduleAtFixedRate(new MyRunnable2(1),1,2,TimeUnit.SECONDS);
System.out.println("over");
}
}
class MyRunnable2 implements Runnable{
private int id;
public MyRunnable2(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"执行了任务:"+id);
}
}
8.16丶Java内置线程池-异步计算结果(Future)
我们刚刚在学习java内置线程池使用时,没有考虑线程计算的结果,但开发中,我们有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future 对象获取线程计算的结果;
Future 的常用方法如下:
boolean cancel(boolean mayInterruptIfRunning)
试图取消对此任务的执行。
V get()
如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。
boolean isDone()
如果任务已完成,则返回 true。
package com.itheima.demo04;
import java.util.concurrent.*;
/*
练习异步计算结果
*/
public class FutureDemo {
public static void main(String[] args) throws Exception {
//1:获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2:创建Callable类型的任务对象
Future<Integer> f = es.submit(new MyCall(1, 1));
//3:判断任务是否已经完成
//test1(f);
boolean b = f.cancel(true);
//System.out.println("取消任务执行的结果:"+b);
//Integer v = f.get(1, TimeUnit.SECONDS);//由于等待时间过短,任务来不及执行完成,会报异常
//System.out.println("任务执行的结果是:"+v);
}
//正常测试流程
private static void test1(Future<Integer> f) throws InterruptedException, ExecutionException {
boolean done = f.isDone();
System.out.println("第一次判断任务是否完成:"+done);
boolean cancelled = f.isCancelled();
System.out.println("第一次判断任务是否取消:"+cancelled);
Integer v = f.get();//一直等待任务的执行,直到完成为止
System.out.println("任务执行的结果是:"+v);
boolean done2 = f.isDone();
System.out.println("第二次判断任务是否完成:"+done2);
boolean cancelled2 = f.isCancelled();
System.out.println("第二次判断任务是否取消:"+cancelled2);
}
}
class MyCall implements Callable<Integer>{
private int a;
private int b;
//通过构造方法传递两个参数
public MyCall(int a, int b) {
this.a = a;
this.b = 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;
}
}
8.17丶综合案例-秒杀商品
案例介绍:
假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢,假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:既然商品总数量是10个,那么我们可以在创建线程池的时候初始化线程数是10个及以下,设计线程池最大数量为10个;
2:当某个线程执行完任务之后,可以让其他秒杀的人继续使用该线程参与秒杀;
3:使用synchronized控制线程安全,防止出现错误数据;
代码步骤:
1:编写任务类,主要是送出手机给秒杀成功的客户;
2:编写主程序类,创建20个任务(模拟20个客户);
3:创建线程池对象并接收20个任务,开始执行任务;
package com.itheima.demo05;
/*
任务类:
包含了商品数量,客户名称,送手机的行为;
*/
public class MyTask implements Runnable {
//设计一个变量,用于表示商品的数量
private static int id = 10;
//表示客户名称的变量
private String userName;
public MyTask(String userName) {
this.userName = userName;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在使用"+name+"参与秒杀任务...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(id>0){
System.out.println(userName+"使用"+name+"秒杀:"+id-- +"号商品成功啦!");
}else {
System.out.println(userName+"使用"+name+"秒杀失败啦!");
}
}
}
}
package com.itheima.demo05;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
主程序类,测试任务类
*/
public class MyTest {
public static void main(String[] args) {
//1:创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>(15));
//2:循环创建任务对象
for (int i = 1; i <=20 ; i++) {
MyTask myTask = new MyTask("客户"+i);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
案例介绍:
设计一个程序,使用两个线程模拟在两个地点同时从一个账号中取钱,假如卡中一共有1000元,每个线程取800元,要求演示结果一个线程取款成功,剩余200元,另一个线程取款失败,余额不足;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:线程池可以利用Executors工厂类的静态方法,创建线程池对象;
2:解决线程安全问题可以使用synchronized方法控制取钱的操作
3:在取款前,先判断余额是否足够,且保证余额判断和取钱行为的原子性;
package com.itheima.demo06;
public class MyTask implements Runnable {
//用户姓名
private String userName;
//取款金额
private double money;
//总金额
private static double total = 1000;
public MyTask(String userName, double money) {
this.userName = userName;
this.money = money;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在准备使用"+name+"取款:"+money+"元");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(total-money>0){
System.out.println(userName+"使用"+name+"取款:"+money+"元成功,余额:"+(total-money));
total-=money;
}else {
System.out.println(userName+"使用"+name+"取款:"+money+"元失败,余额:"+total);
}
}
}
}
package com.itheima.demo06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class MyTest {
public static void main(String[] args) {
//1:创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
int id = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ATM" + id++);
}
});
//2:创建两个任务并提交
for (int i = 1; i <=2 ; i++) {
MyTask myTask = new MyTask("客户" + i, 800);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
9丶线程池总结
线程池的使用步骤可以归纳总结为五步 :
- 利用Executors工厂类的静态方法,创建线程池对象;
- 编写Runnable或Callable实现类的实例对象;
- 利用ExecutorService的submit方法或ScheduledExecutorService的schedule方 法提交并执行线程任务
- 如果有执行结果,则处理异步执行结果(Future)
- 调用shutdown()方法,关闭线程池