声明:该专栏本人重新过一遍java知识点时候的笔记汇总,主要是每天的知识点+题解,算是让自己巩固复习,也希望能给初学的朋友们一点帮助,大佬们不喜勿喷(抱拳了老铁!)
往期回顾
Java学习day27:join方法、生产者消费者模式(知识点详解)-CSDN博客
Java学习day26:和线程相关的Object类的方法、等待线程和唤醒线程(知识点详解)-CSDN博客
Java学习day25:守护线程、死锁、线程生命周期(知识点详解)-CSDN博客
线程池是一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源
在Java语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:
①通过 ThreadPoolExecutor 手动创建线程池。 |
②通过 Executors 执行器自动创建线程池。 |
而以上两类创建线程池的方式,又有 7 种具体实现方法,每种实现方法都有不同特点,而实际开发基本用最后一种,前六种一半不用的,所以前面的大家以了解为主,但是也要知道,重点在于最后一个。
我们今天,先讲前六种,最后一种太重要知识点也太多了,我们单独拿出来讲。
创建一个固定大小的线程池,可控制并发线程数。使用FixedThreadPool创建2个固定大小的线程池,执行任务的方法有两种:submit 和 execute,具体实现代码:
示例1:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo3 {
public static void main(String[] args) {
fixedThreadPool();
}
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 个任务)
threadPool.submit(runnable); // 执行方式 1:submit
threadPool.execute(runnable); // 执行方式 2:execute
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}
示例2:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo2 {
public static void main(String[] args) {
//1.创建线程池 创建了两个线程
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//2.任务
Runnable run1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("晚上吃饭。线程为:" + Thread.currentThread().getName());
}
}
};
Runnable run2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("晚上睡觉。线程为:" + Thread.currentThread().getName());
}
}
};
Runnable run3 = new Runnable() {
@Override
public void run() {
//run方法中写的是需求!!
for (int i = 0; i < 20; i++) {
System.out.println("晚上敲代码不睡觉。线程为:" + Thread.currentThread().getName());
}
}
};
//3.执行上上面的三个任务
threadPool.submit(run1);
threadPool.execute(run2);
threadPool.execute(run3);
}
}
注意点:
Ⅰ.都可以概括为三个步骤:
①创建固定数量的线程池 |
②使用Runnable匿名内部类创建任务 |
③调用submit 或 execute执行任务 |
Ⅱ.run方法里面写的是任务需求,业务实现。
Ⅲ.记住,执行任务的时候,线程依旧是抢占式运行的。
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
cachedThreadPool();
}
public static void cachedThreadPool() {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 执行任务
for (int i = 0; i < 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}
使用场景:
CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。
创建单个线程数的线程池,它可以保证先进先出的执行顺序。其底层实现是队列
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
singleThreadExecutor();
}
public static void singleThreadExecutor() {
// 创建线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + ":任务被执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
});
}
}
}
单个线程的线程池有什么意义?单个线程的线程池相比于线程来说,它的优点有以下 2 个:
Ⅰ.可以复用线程:即使是单个线程池,也可以复用线程。
Ⅱ.提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。
创建一个可以执行延迟任务的线程池。
示例
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
scheduledThreadPool();
}
public static void scheduledThreadPool() {
// 创建线程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
// 添加定时执行任务(1s 后执行)
System.out.println("添加任务,时间:" + new Date());
threadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务被执行,时间:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
, 3, TimeUnit.SECONDS);
}
}
threadPool.schedule(1,2,3)里面的三个参数,1是需要执行的任务,2是延迟执行时间,3是延迟执行时间单位,这里是秒。
创建一个单线程的可以执行延迟任务的线程池。此线程池可以看作是 ScheduledThreadPool 的单线程池版本。
示例
ublic 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);
}
创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】
示例
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo3 {
public static void main(String[] args) {
workStealingPool();
}
public static void workStealingPool() {
// 创建线程池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 执行任务
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
}
});
}
// 确保任务执行完成
while (!threadPool.isTerminated()) {
}
}
}
从上述结果可以看出,任务的执行顺序是不确定的,因为它是抢占式执行的。
手动创建线程池的方式,它创建时最多可以设置7个参数。(开发中用,面试要考)这一个非常非常重要,我们拿出来,单独用一篇文章讲。
线程池的创建方式总共有以下 7 种:
1. Executors.newFixedThreadPool | 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。 |
2. Executors.newCachedThreadPool | 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。 |
3. Executors.newSingleThreadExecutor | 创建单个线程数的线程池,它可以保证先进先出的执行顺序。 |
4. Executors.newScheduledThreadPool | 创建一个可以执行延迟任务的线程池。 |
5. Executors.newSingleThreadScheduledExecutor | 创建一个单线程的可以执行延迟任务的线程池。 |
6. Executors.newWorkStealingPool | 创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】 |
7. ThreadPoolExecutor | 手动创建线程池的方式,它创建时最多可以设置 7 个参数。 |
而**线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险**。
以上,就是今天的所有知识点了。线程池是Java中非常非常重要的核心部分,前六种具体创建线程池的方式,即使开发用的不多,大家也需要了解掌握,大家要自己多花点时间,静下心看代码,写代码,多理解,多运用,重点是多去运用。
加油吧,预祝大家变得更强!