进程和线程

  • 进程(Process)是计算机中的程序关于某数据集合上的一次运动活动
    • 是系统进行资源分配的基本单位
  • 简单理解:程序的执行过程
  1. 独立性:每一个进程都有自己的空间,在没有经过进程本身允许的情况下,一个进程不可以直接访问其他的进程空间
  2. 动态性:进程是动态产生的,动态消亡的
  3. 并发性:任何进程都可以同其他进程一起并发执行

并行和并发

  • 并行:在同一时刻,有多个指令在CPU上**同时**执行
  • 并发:在同一时刻,有多个指令在CPU上交替执行

多进程同时工作

对于一个CPU而言,它是在多个线程间轮换执行的

线程

线程(Thread):进程可以同时执行多个任务,每个任务就是线程

多线程的意义

  • 随着处理器上的核心数量越来越多,现在大多数计算机都比以往更加擅长并行计算
  • 一个线程,在一个时刻,只能运行在一个处理器核心上
  • 提高执行效率
  • 同时处理多个任务

img_004

多线程_01

Java开启线程方式

  • Java默认是多线程,当运行时默认会存在main线程垃圾回收线程

开启线程,有以下三种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

继承Thread类

步骤如下:

  1. 编写一个类继承Thread类
  2. 重写run方法
  3. 将线程任务代码写在run方法中
  4. 创建线程对象
  5. 调用start方法开启线程(调用start方法后,会自动调用run方法)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package thread;

public class ThreadDemo01 {
public static void main(String[] args) {
// 4. 创建线程对象
MyThread01 mt = new MyThread01();

// 5. 调用start方法开启线程
mt.start();

// 区分线程
for (int i = 0; i < 500; i++) {
System.out.println("main线程执行了" + i); // main线程和对象线程交替执行
}
}
}

// 1. 编写一个类继承Thread类
class MyThread01 extends Thread {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码写在run方法中
for (int i = 0; i < 500; i++) {
System.out.println("线程任务执行了" + i);
}
}
}

实现Runnable接口

  1. 编写一个类实现Runnable接口
  2. 重写run方法
  3. 将线程任务代码写在run方法中
  4. 创建Runnable接口的实现类对象
  5. 创建线程对象,并将Runnable接口的实现类对象传入
  6. 使用线程对象调用start方法开启线程

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package thread;

public class ThreadDemo02 {
public static void main(String[] args) {
// 4. 创建Runnable接口的实现类对象
MyRunnable01 mr = new MyRunnable01();

// 5. 创建线程对象,并将Runnable接口的实现类对象传入
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);

// 6. 使用线程对象调用start方法开启线程
t1.start();
t2.start();

// 区分线程
for (int i = 0; i < 500; i++) {
System.out.println("main线程执行了:" + i);
}
}
}

// 1. 编写一个类实现Runnable接口
class MyRunnable01 implements Runnable {
// 2. 重写run方法
@Override
public void run() {
// 3. 将线程任务代码写在run方法中
for (int i = 0; i < 500; i++) {
System.out.println("自己的线程任务:" + i);
}
}
}

实现Callable接口

  1. 编写一个类实现Callable接口(需要指定泛型,根据call方法返回的类型确定类型)
  2. 重写call方法(此方法存在返回值)
  3. 将线程任务代码写在call方法中
  4. 创建线程资源对象
  5. 创建线程任务对象,封装线程资源
  6. 创建线程对象,传入线程任务
  7. 使用线程对象调用start开启线程

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 4. 创建线程资源对象
MyCallable01 mc = new MyCallable01();

// 5. 创建线程任务对象,封装线程资源
FutureTask<Integer> task1 = new FutureTask<>(mc);

// 6. 创建线程对象,传入线程任务
Thread t1 = new Thread(task1);

// 7. 使用线程对象调用start开启线程
t1.start();

for (int i = 1; i <= 100; i++) {
System.out.println("main线程执行中:" + i);
}

// 8. 获取返回结果
Integer res = task1.get();
System.out.println("线程执行结果:" + res);
}
}

// 1. 编写一个类实现Callable接口
class MyCallable01 implements Callable<Integer> {
// 2. 重写call方法
@Override
public Integer call() throws Exception {
// 3. 将线程任务代码写在call方法中
int count = 0;

for (int i = 1; i <= 100; i++) {
count += i;
System.out.println("线程执行中:" + i);
}

return count;
}
}

FutureTask类实现了RunnableFuture接口,这个接口继承了Runnable接口

调用get()方法获取线程的执行结果,要注意必须在调用了start()方法之后(不然线程都没运行就直接要求拿到结果,就会卡住)

三种方式的选择

第一种,继承Thread类:实现简单,代码少,但是由于Java只支持单继承,所以当继承了Thread类后,就不能再继承其他类了

第二种,实现Runnable接口:实现起来虽然较复杂,但是比继承更加灵活,且实际开发中用的较多

第三种,实现Callable接口:当需要线程执行完后返回结果,可以使用这种方式,因为call方法有返回值

综上所述:第二种和第三种的优势更符合开发项目,实际开发中也使用的更多

线程相关方法

方法名称 说明
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
static Thread currentThread() 获取当前线程的对象
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
final void setDaemon(boolean on) 设置为守护线程

获取线程名称

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package thread;

public class ThreadDemo04 {
public static void main(String[] args) {
MyThread02 mt01 = new MyThread02();
MyThread02 mt02 = new MyThread02();

mt01.start();
mt02.start();
}
}

class MyThread02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(super.getName() + "线程执行中:" + i);
}
}
}

线程如果不手动设置名称,那么就为默认名称:Thread-数字

比如:第一个线程名称为Thread-01,第二个线程名称为Thread-02

设置线程名称

线程的名称是可以重复的

利用setName设置线程名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package thread;

public class ThreadDemo04 {
public static void main(String[] args) {
MyThread02 mt01 = new MyThread02();
MyThread02 mt02 = new MyThread02();

mt01.setName("线程A:");
mt02.setName("线程B:");

mt01.start();
mt02.start();
}
}

class MyThread02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(super.getName() + "线程执行中:" + i);
}
}
}

利用构造方法设置线程名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package thread;

public class ThreadDemo04 {
public static void main(String[] args) {
MyThread02 mt01 = new MyThread02();
MyThread02 mt02 = new MyThread02("线程A:");
MyThread02 mt03 = new MyThread02("线程B:");


mt01.start();
mt02.start();
mt03.start();
}
}

class MyThread02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(super.getName() + "线程执行中:" + i);
}
}

public MyThread02() {
super();
}

public MyThread02(String name) {
super(name);
}
}

以上两种方式都继承了Thread类,所以可以直接调用**super.getName()**方法获取线程名称

但是当类实现Runnable或Callable接口时,不能调用super.getName(),因为这个类的父类没有继承Thread类

类实现接口时,获取线程名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package thread;

public class ThreadDemo05 {
public static void main(String[] args) {
MyRunnable02 mr01 = new MyRunnable02();
Thread t1 = new Thread(mr01,"线程A:");

MyRunnable02 mr02 = new MyRunnable02();
Thread t2 = new Thread(mr02);
t2.setName("线程B:");

t1.start();
t2.start();
}
}

class MyRunnable02 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行中:" + i);
}
}


}

创建线程对象时,可以接收两个参数,第一个参数是Runnable实现类对象,第二参数是线程名称

获取当前线程对象

示例:

1
2
3
4
5
6
7
8
9
package thread;

public class ThreadDemo06 {
public static void main(String[] args) {
System.out.println(Thread.currentThread()); // 获取main的线程对象
System.out.println(Thread.currentThread().getName()); // 获取main的线程对象的名称
}
}

让线程休眠指定时间

示例:

1
2
3
4
5
6
7
8
9
10
package thread;

public class ThreadDemo07 {
public static void main(String[] args) throws InterruptedException {
for (int i = 5; i != 0; i--) {
Thread.sleep(1000); // 休眠1000毫米
System.out.println("倒计时:" + i);
}
}
}

设置线程优先级

线程调度方式

  • 抢占式调度
    • Java中默认采用的就是这种方式
    • 哪个线程抢到就执行哪个线程
  • 非抢占式调度
    • 每个线程依次执行
方法名称 说明
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级

优先级:1 ~ 10,默认为5,优先级越高,抢到CPU执行的概率越高

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package thread;

public class ThreadDemo08 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "---执行执行中---" + i);
}
}
},"线程A:");

Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "---执行执行中---" + i);
}
}
}, "线程B:");

System.out.println(t1.getPriority()); // 5
System.out.println(t2.getPriority()); // 5

t1.setPriority(1);
t2.setPriority(10);

System.out.println(t1.getPriority()); // 1
System.out.println(t2.getPriority()); // 10

t1.start();
t2.start(); // 线程B先执行完毕的概率更高
}
}

设置优先级只是提高抢到CPU执行的概率,而不是哪个线程一定会先拿到CPU执行

守护线程

  • 当一条线程被关闭,那么另一条线程也要随着这条线程的关闭而关闭,这就是守护线程

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package thread;

public class ThreadDemo09 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--- 线程执行中 ---" + i);
}
}
},"线程A:");

Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "--- 线程执行中 ---" + i);
}
}
},"线程B:");

t2.setDaemon(true);
t1.start();
t2.start();
}
}

设置守护线程不需要将守护线程与被守护线程显式关联

整个执行流程如下:

  1. main线程启动t1和t2
  2. t1(用户线程)执行20次循环
  3. t2(守护线程)执行循环(可能执行不完)
  4. 当t1执行完毕后,JVM发现没有其他用户线程在运行
  5. JVM退出,强制终止t2(即使t2还没执行完200次)

线程安全和同步

线程的安全问题

安全问题出现条件:

  • 多线程环境
  • 存在共享数据
  • 有多条语句操作共享数据

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package thread;

public class ThreadDemo10 {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票
请设计一个程序模拟电影院买票
*/
public static void main(String[] args) {
TicketTask.tickets = 100;
TicketTask ticketTask_01 = new TicketTask();
TicketTask ticketTask_02 = new TicketTask();
TicketTask ticketTask_03 = new TicketTask();

Thread t1 = new Thread(ticketTask_01,"一号窗口:");
Thread t2 = new Thread(ticketTask_02,"二号窗口:");
Thread t3 = new Thread(ticketTask_03,"三号窗口:");


t1.start();
t2.start();
t3.start();

}
}

class TicketTask implements Runnable {
static int tickets;
@Override
public void run() {
while (tickets != 0) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - tickets) + "张票");
}
}
}

问题如下:

多线程_02

假设线程1在执行过程中,CPU执行权被线程二抢到了,那么就会进入到线程二的处理逻辑中。此时数据还未被线程1修改,所以线程1和线程2在操作同一份数据,这就会出问题

线程同步

  • 同步技术:将多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程可以执行
    • 同步代码块
    • 同步方法
    • Lock锁

同步代码块

关键字:synchronized [ˈsɪŋkrənaɪzd]

格式:

1
2
3
synchronized(锁对象) {
多条语句操作共享数据代码
}

锁对象可以是任意对象,但是需要保证多条线程的锁对象,是同一把锁

同步可以解决多线程的数据安全问题,但是也会降低程序的运行效率

将线程的安全问题这一章的代码进行优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package thread;

import java.util.Objects;

public class ThreadDemo11 {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票
请设计一个程序模拟电影院买票
*/
public static void main(String[] args) {
TicketTaskAfterFix.tickets = 100;
TicketTaskAfterFix ticketTask_01 = new TicketTaskAfterFix();

Thread t1 = new Thread(ticketTask_01,"一号窗口:"); // 创建第一个线程
Thread t2 = new Thread(ticketTask_01,"二号窗口:"); // 创建第二个线程
Thread t3 = new Thread(ticketTask_01,"三号窗口:"); // 创建第三个线程


t1.start();
t2.start();
t3.start();

}
}

class TicketTaskAfterFix implements Runnable {
static int tickets;
Object lock = new Object();

@Override
public void run() {
while (tickets != 0) {
synchronized (lock) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - tickets) + "张票");
}
}
}
}

同步代码块要在while内部

表示线程A进入while内部后,先上锁,执行完ticket–操作后,打印控制台,最后释放锁

A线程 => 进入while内部 => 上锁 => 开始操作逻辑 => 执行完成,释放锁

当同步代码块在while外部

表示线程A先上锁,然后执行while逻辑代码,直到所有票数为0,才释放锁,其他线程没有机会参与进去

A线程 => 先上锁 => 执行while逻辑代码 => 票数减到0 => 释放锁

注意:这里一定只能创建一个Runnable实现类对象,否则会导致生成的锁不是一个锁。一定要保证锁的一致性

为了保证锁的一致性,更加推荐直接使用当前的类对象作为锁对象,即XXX.class

1
2
3
4
5
6
7
8
9
@Override
public void run() {
while (tickets != 0) {
synchronized (TicketTaskAfterFix.class) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - tickets) + "张票");
}
}
}

同步方法

在方法的返回值类型前面加入synchronized关键字

方法分为静态和非静态

静态方法的锁对象是字节码对象,非静态方法的锁对象是this

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package thread;

import java.util.Objects;

public class ThreadDemo11 {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票
请设计一个程序模拟电影院买票
*/
public static void main(String[] args) {
TicketTaskAfterFix.tickets = 100;
TicketTaskAfterFix ticketTask_01 = new TicketTaskAfterFix();


Thread t1 = new Thread(ticketTask_01,"一号窗口:");
Thread t2 = new Thread(ticketTask_01,"二号窗口:");
Thread t3 = new Thread(ticketTask_01,"三号窗口:");


t1.start();
t2.start();
t3.start();

}
}

class TicketTaskAfterFix implements Runnable {
static int tickets;

@Override
public void run() {
while (true) {
if (!sellTickets()) {
return;
}
}
}

private synchronized boolean sellTickets() {
if (tickets == 0) return false;
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - tickets) + "张票");
return true;
}
}

这里只创建了一个Runnable实现类对象,所以三个线程的锁对象都是当前对象,保证了锁的一致性
但如果创建了多个Runnable实现类对象,那么这几个对象的this并不是同一个对象,无法保证锁的一致性,此时只能将方法转换为静态方法

创建方式 同步方法类型 锁对象 是否有效
多个实例 实例方法 各实例自身 ❌ 无效
多个实例 静态方法 类对象 ✅ 有效
单个实例 实例方法 该实例 ✅ 有效
单个实例 静态方法 类对象 ✅ 有效

Lock锁

  • 使用Lock锁,可以更清晰的看到哪里加了锁,哪里释放了锁

  • 先前的同步代码块synchronized (锁对象) { ... }、同步方法public synchornized void methd() {},无法直观的看到上锁、释放锁的过程

  • Lock锁可以解决以上问题:

    1
    2
    3
    lock.lock();
    ...
    lock.unlock();
  • Lock是接口,无法直接创建对象

构造方法 说明
public ReentrantLock() 创建一个ReentrantLock的实力
成员方法 说明
void lock() 加锁
void unlock() 释放锁

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package thread;

import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo11 {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口买票
请设计一个程序模拟电影院买票
*/
public static void main(String[] args) {
TicketTaskAfterFix.tickets = 100;
TicketTaskAfterFix ticketTask_01 = new TicketTaskAfterFix();


Thread t1 = new Thread(ticketTask_01,"一号窗口:");
Thread t2 = new Thread(ticketTask_01,"二号窗口:");
Thread t3 = new Thread(ticketTask_01,"三号窗口:");


t1.start();
t2.start();
t3.start();

}
}

class TicketTaskAfterFix implements Runnable {
static int tickets;
ReentrantLock rtl = new ReentrantLock();

@Override
public void run() {

while (true) {
rtl.lock();
if (tickets == 0) break;
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - tickets) + "张票");
rtl.unlock();
}
rtl.unlock(); // 最后一次的锁也需要释放,否则程序会卡主
}
}

线程池

  • 将线程对象交给线程池维护,可以降低系统成本,从而提升程序的性能

系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互
当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源

【强制】 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

【强制】 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽风险

说明:Executor返回的线程池对象的弊端如下:

  1. FixedThreadPoolSingleThreadPool
    • 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(OutOfMemory)
  2. CachedThreadPool
    • 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

使用JDK提供的线程池

Executors中提供静态方法来创建线程池

方法 介绍
static ExecutorService newCachedThreadPool() 创建一个默认的线程池
static newFixedThreadPool(int nThreads) 创建一个指定最大线程数量的线程池

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package pool;

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

public class ThreadPoolDemo01 {
public static void main(String[] args) {
// 获取指定线程数量的线程池对象
ExecutorService pool2 = Executors.newFixedThreadPool(10);

for (int i = 0; i < 100; i++) {
// 多次提交线程任务,会复用线程池中的线程对象,且线程名称的数字不会超过10
pool2.submit(new Runnable() {
@Override
public void run() {
// 程序运行完成,JVM不会停止,除非关闭线程池
System.out.println(Thread.currentThread().getName() + "提交了线程任务");
}
});
}

pool2.shutdown();
}

public static void getPool() {
// 获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();

// 提交线程任务到线程池
for (int i = 0; i < 100; i++) {
// 多次提交线程任务,会复用线程池中的线程对象
pool1.submit(new Runnable() {
@Override
public void run() {
// 程序运行完成,JVM不会停止,除非关闭线程池
System.out.println(Thread.currentThread().getName() + "提交了线程任务");
}
});
}

// 关闭线程池
pool1.shutdown();
}
}

自定义线程池

创建ThreadPoolExecutor类对象,用于自定义线程池

构造方法 说明
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
corePoolSize:核心线程数量(正式员工)
maximumPoolSize:最大线程数量(正式员工 + 临时员工)
keepAliveTime:空闲时间
unit:时间单位
workQueue:任务队列(指定排队人数)
threadFactory:线程对象工厂
handler:拒绝策略

注意事项:

  1. 核心数量不能为0
  2. 最大线程数量不能为0,且数量必须>=核心线程数量
  3. 空闲时间不能小于0(不能为负数)
  4. 时间单位必须使用TimeUnit枚举类型
  5. 任务队列不能为null
  6. 线程对象工厂不能为null
  7. 拒绝策略不能为null

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package pool;

import java.util.concurrent.*;

public class ThreadPoolDemo02 {
public static void main(String[] args) {
// 任务队列分为有界队列和无界队列
// 有界队列:new ArrayBlockingQueue<>(10),参数表示队伍最大长度
// 无界队列:new LinkedBlockingQueue<>()
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(), // 基本上是固定写法
new ThreadPoolExecutor.AbortPolicy());

// 当超过了核心 + 队列长度,就会尝试创建临时线程
for (int i = 1; i <= 13; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了任务");
}
});
}

// 触发拒绝策略,控制台会警告
for (int i = 1; i <= 16; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了任务");
}
});
}
}
}

提交的任务线程数大于了核心线程数+任务队列长度,就会创建临时线程(线程任务数 > 核心线程数 + 任务队列的数量
当线程任务数大于了最大线程数+任务队列数,就会启动拒绝策略(线程任务数 > 最大线程数 + 任务队列的数量

线程拒绝策略

策略选项 说明
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常(默认且推荐)
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常(不推荐)
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 调用任务的run方法,绕过线程池直接执行