1.异常
Java异常体系
Throwable
Error和Exception
编译时异常和运行时异常
受检异常和非受检异常
常见的错误和异常
Error
运行时异常
import org.junit.Test;
import java.util.Scanner;
public class TestRuntimeException {
@Test
public void test01(){
//NullPointerException
int[][] arr = new int[3][];
System.out.println(arr[0].length);
}
@Test
public void test02(){
//ClassCastException
Object obj = 15;
String str = (String) obj;
}
@Test
public void test03(){
//ArrayIndexOutOfBoundsException
int[] arr = new int[5];
for (int i = 1; i <= 5; i++) {
System.out.println(arr[i]);
}
}
@Test
public void test04(){
//InputMismatchException
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");//输入非整数
int num = input.nextInt();
input.close();
}
@Test
public void test05(){
int a = 1;
int b = 0;
//ArithmeticException
System.out.println(a/b);
}
}
编译时异常
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class TestCheckedException {
@Test
public void test06() {
Thread.sleep(1000);//休眠 1 秒 InterruptedException
}
@Test
public void test07(){
Class c = Class.forName("java.lang.String");//ClassNotFoundEx
ception
}
@Test
public void test08() {
Connection conn = DriverManager.getConnection("...."); //SQL
Exception
}
@Test
public void test09() {
FileInputStream fis = new FileInputStream("尚硅谷 Java 秘籍.txt
"); //FileNotFoundException
}
@Test
public void test10() {
File file = new File("尚硅谷 Java 秘籍.txt");
FileInputStream fis = new FileInputStream(file);//FileNotFound
Exception
int b = fis.read();//IOException
while(b != -1){
System.out.print((char)b);
b = fis.read();//IOException
}
fis.close();//IOException
}
}
异常的处理
Java异常处理(两种方式)
try-catch-finally捕获异常
确保资源关闭
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestFinally {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入第一个整数:");
int a = input.nextInt();
System.out.print("请输入第二个整数:");
int b = input.nextInt();
int result = a/b;
System.out.println(a + "/" + b +"=" + result);
} catch (InputMismatchException e) {
System.out.println("数字格式不正确,请输入两个整数");
}catch (ArithmeticException e){
System.out.println("第二个整数不能为 0");
} finally {
System.out.println("程序结束,释放资源");
input.close();
}
}
@Test
public void test1(){
FileInputStream fis = null;
try{
File file = new File("hello1.txt");
fis = new FileInputStream(file);//FileNotFoundException
int b = fis.read();//IOException
while(b != -1){
System.out.print((char)b);
b = fis.read();//IOException
}
}catch(IOException e){
e.printStackTrace();
}finally{
try {
if(fis != null)
fis.close();//IOException
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
throws抛出异常
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestThrowsRuntimeException {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
System.out.print("请输入第一个整数:");
int a = input.nextInt();
System.out.print("请输入第二个整数:");
int b = input.nextInt();
int result = divide(a,b);
System.out.println(a + "/" + b +"=" + result);
} catch (ArithmeticException | InputMismatchException e) {
e.printStackTrace();
} finally {
input.close();
}
}
public static int divide(int a, int b)throws ArithmeticException{
return a/b;
}
}
方法重写中对throws的要求
两种异常处理方式的选择
手动抛出异常对象:throw
使用格式
注意点
自定义异常
注意点
举例 1:
class MyException extends Exception {
static final long serialVersionUID = 23423423435L;
private int idnumber;
public MyException(String message, int id) {
super(message);
this.idnumber = id;
}
public int getId() {
return idnumber;
}
}
public class MyExpTest {
public void regist(int num) throws MyException {
if (num < 0)
throw new MyException("人数为负值,不合理", 3);
else
System.out.println("登记人数" + num);
}
public void manager() {
try {
regist(100);
} catch (MyException e) {
System.out.print("登记失败,出错种类" + e.getId());
}
System.out.print("本次登记操作结束");
}
public static void main(String args[]) {
MyExpTest t = new MyExpTest();
t.manager();
}
}
小结
2.多线程
概念
程序、进程与线程
其实不用背那么拗口的概念,一句话,有了多线程,可以让程序在同一时间做多件事情
简单理解线程:应用软件中互相独立,可以同时运行的功能
查看进程和线程
- 每个应用程序的运行都是一个进程
- 一个应用程序的多次运行,就是多个进程
- 一个进程中包含多个线程
线程调度
多线程程序的优点
并发和并行
- 并发与并行有可能同时在发生
多线程的实现方式(三种)
(1)继承Thread类
(2)实现Runnable接口
(3)利用Callable接口和Future接口
前两种方法重写run()都是void无返回值的,无法获得run()运行的结果,而 ...
多线程中的常用成员方法
线程名称
可以通过setName设置名称
可以通过构造器设置名称
默认名称是Thread-X(X是需要,从0开始的)
可以通过getName获取名称
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们所写的代码,都是运行在main线程当中
获取当前线程对象
线程休眠
线程优先级
Java中,线程优先级默认是5,最小是1,最大是10
- 优先级越大,抢占cpu的概率越高
- 记住,只是概率越高,而不是百分百能抢到cpu的执行权,多运行几次试试看
线程调度
在计算机当中,线程调度分为 抢占式调度 和 非抢占式调度
- 抢占式调度(随机性):多个线程抢夺cpu的执行权,cpu在同一时刻执行哪一个线程是不确定的,执行多长时间也是不确定的
- 非抢占式调度(有序性):多个线程轮流的执行,你一次我一次,执行的时间也是差不多的
在Java中,采用的是抢占式调度
守护线程
非守护线程执行完毕后,守护线程没有存在的必要,会陆续结束
如何理解陆续结束?
比如女神线程打印100个数,守护线程也打印100个数,当女神线程打印完后,守护线程没有存在的必要了就会自动进入死亡状态,但是在进入之前的一瞬间还是能执行部分线程程序的,比如打印了30个数就结束了
礼让线程
为了让多个线程抢占cpu执行权,概率尽量均匀一些
注意不是完全均匀!!
插队线程
线程的生命周期
就绪状态:有抢cpu的资格但是还没抢到,所以没有执行权
其实应该还有wait和notify还有阻塞与锁,这里老师并没有讲,不知道为啥
同步代码块
将操作共享数据的代码块锁起来,让所有线程轮流执行这段代码块
多线程处理同一数据:多窗口售票
public class MyThread extends Thread {
// 被多线程操作的共享数据
static int ticket = 0;// 0 ~ 99
@Override
public void run() {
while (true) {
if (ticket < 10) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "售出第:" + ticket + "张门票。");
}else {
break;
}
}
}
}
public class MyTest {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而有三个售票窗口,模拟程序。
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
代码逻辑上似乎没问题,每次某个窗口卖出一张票时,就会调用sleep()睡眠100ms,避免卖票太快,但是运行结果发现
问题思考
多线程处理同一数据引发的问题:
- 相同的数据出现了多次
- 出现了超出范围的数据
解决思路
当有一个线程抢到执行权,就锁住这块代码,就算其他线程也抢到了执行权,也要等着,等执行完,其他线程才能轮流进入
将线程的随机性变成有序性
Synchronized锁
锁对象,一定要是唯一的,确定锁的唯一
修改源代码(两种方式)
修改上文源代码
① 继承Thread类
public class MyTest {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而有三个售票窗口,模拟程序。
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class MyThread extends Thread {
// 被多线程操作的共享数据
static int ticket = 0;// 0 ~ 99
// 锁对象,一定要是唯一的
static Object obj = new Object();
@Override
public void run() {
while (true) {
// 同步代码块
synchronized (obj) {
if (ticket < 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "售出第:" + ticket + "张门票。");
}else {
break;
}
}
}
}
}
② 实现Runnable接口
public class MyTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class MyRunnable implements Runnable{
// 这种实现方式反正也就创建一次对象,不需要static修饰
int ticket = 0;
@Override
public void run() {
// 1.循环
while (true) {
// 2.同步代码块(同步方法)
synchronized (MyRunnable.class) {
// 3.判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
break;
}else {
// 4.如果没到末尾
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票。");
}
}
}
}
}
同步方法
给上文代码抽取成方法
public class MyRunnable implements Runnable{
// 这种实现方式反正也就创建一次对象,不需要static修饰
int ticket = 0;
@Override
public void run() {
// 1.循环
while (true) {
// 2.同步代码块(同步方法)
if (method()) break;
}
}
// 是非静态方法,锁对象是this
private synchronized boolean method() {
// 3.判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
return true;
}else {
// 4.如果没到末尾
try {
Thread.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票。");
}
return false;
}
}
public class MyTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
线程销毁
Lock锁
Synchronized锁的上锁和释放锁都是自动的,Lock锁提供手动上锁和手动释放锁
下面这个例子的代码逻辑错了,不过lock的使用没问题可以看
public class MyThread extends Thread{
static int ticket = 0;
// static Object obj = new Object();
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
// synchronized (obj) {
try {
// 同步代码块
if (ticket < 100) {
Thread.sleep(30);
ticket++;
System.out.println(getName() + "售出第:" + ticket + "张门票。");
}else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
// }
}
}
}
package com.autism;
/**
* @Author oi
* @Date 2024/8/4 5:01
* @Description:
*/
public class MyTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁(一种错误)
两个线程分别拿着自己的锁,并且等着对方释放锁,从而卡住
锁的嵌套容易导致死锁
等待唤醒机制(两种)
生产者/消费者实现
Java的线程调度是抢占式调度,具有随机性,生产者消费者模式将打破这种机制,变成轮流执行,你一次我一次
理想情况
- 厨师抢到了CPU的执行权,发现桌上空的,厨师将饭端上桌,释放线程
- 顾客抢到了CPU执行权,顾客吃饭
厨师端一碗,顾客吃一碗,厨师再端一碗,顾客再吃一碗
消费者等待(wait¬ify)
但是Java的线程调度具有随机性,程序不可能这么听话,不总会按照我们想着的这么听话
- 顾客先抢到了CPU的执行权,发现桌上空的,于是进入等待状态(wait)
- 此时厨师就可以抢到CPU的执行权,发现桌上空的,端上了一碗饭
- 可惜此时顾客依旧处于wait,厨师还需要一次唤醒(notify),提醒顾客吃饭
生产者等待
- 厨师先抢到了CPU的执行权,发现桌上空的,端上一碗饭,唤醒(notify)
- 厨师02紧跟着抢到了CPU的执行权,发现桌上有饭,只能等着(wait)
完整的生产者消费者机制
常见方法
代码实现
消费者代码实现
代码实现逻辑是有问题的,看看方法使用就好
public class Foodie extends Thread{
@Override
public void run() {
while (true) {
//同步代码块
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
}else {
if (Desk.foodFlag == 0) {
// 如果没有,则等待
try {
Desk.lock.wait(); // 让当前线程跟锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 如果有,则吃掉一碗
Desk.count--;
System.out.println("顾客吃了一碗面条,还剩下" + Desk.count + "碗。");
// 吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
public class Desk {
/**
* 作用:控制生产者和消费者的执行
*
*/
// 是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
// 总个数 (模拟判断条件)
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
生产者代码实现
public class Cook extends Thread{
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
}else {
if (Desk.foodFlag == 1) {
// 有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 没有,就制作食物
System.out.println("制作了一碗");
// 放在桌子上(修改桌子上的食物状态)
Desk.foodFlag = 1;
// 叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
public class Desk {
/**
* 作用:控制生产者和消费者的执行
*
*/
// 是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
// 总个数 (模拟判断条件)
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
编写测试类
public class MyTest {
public static void main(String[] args) {
/**
*
* 需求:完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替执行的效果
*
*/
// 创建线程的对象
Cook c = new Cook();
Foodie f = new Foodie();
// 给线程设置名字
c.setName("厨师");
f.setName("顾客");
// 开启线程
c.start();
f.start();
}
}
阻塞队列实现
阻塞与队列
- 理解阻塞?
- 理解队列
数据在队列中,像是在排队一样,先进先出,后进后出,是可以设置最大值的
阻塞队列的继承结构
代码案例
- 测试类
在测试类中再创建阻塞队列的对象
注意此时输出语句是在锁的外面的,所以输出可能是乱的,但是程序本身没有错误
多线程的四步套路
建议按照这个步骤写多线程,实用率高
多线程的六大状态
Java中并没有运行这个状态,是黑马的阿伟老师自己加的
为什么Java的线程没有运行状态?
当线程抢到CPU执行权的时候,JVM便将此线程交给操作系统,自己不管了
多线程练习题
第一题
public class TicketWindow extends Thread{
// 1000张电影票
static int ticketCount = 1000;
// lock锁
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
// 同步代码块
if (ticketCount == 0) {
break;
}else {
Thread.sleep(3000);
ticketCount--;
System.out.println(getName() + "卖出了1张票,还剩:" + ticketCount + "张票。");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
public class MyTest {
public static void main(String[] args) {
TicketWindow tw1 = new TicketWindow();
TicketWindow tw2 = new TicketWindow();
TicketWindow tw3 = new TicketWindow();
tw1.setName("①号售票窗口");
tw2.setName("②号售票窗口");
tw3.setName("③号售票窗口");
tw1.start();
tw2.start();
tw3.start();
}
}
第二题
public class Gift extends Thread{
static int giftCount = 100;
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
// 同步代码块
if (giftCount < 10) {
break;
}else {
giftCount--;
System.out.println(getName() + "送出了1份礼物,还剩下:" + giftCount + "份。");
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
Gift g1 = new Gift();
Gift g2 = new Gift();
g1.setName("①号送礼人");
g2.setName("②号送礼人");
g1.start();
g2.start();
}
}
第三题
public class Number extends Thread{
static int number = 1;
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (number == 100) {
break;
}else {
if (number % 2 == 1) {
System.out.println(getName() + "输出奇数:" + number);
}
number++;
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
n1.setName("线程1");
n2.setName("线程2");
n1.start();
n2.start();
}
}
第四题
用double实现
这种是有精度损失的,主要是便于新手看代码逻辑,BigDecimal的加减乘除都是用方法实现的不方便看
开发中,有关金额的处理绝对不可以使用double,绝对不可以,绝对不行
public class RedPacket extends Thread{
// 总金额100元
static double money = 100;
// 分成三个红包
static int packetCount = 3;
// 最小的中奖金额:0.01元
static final double MIN_MONEY = 0.01;
static Object obj = new Object();
@Override
public void run() {
// 循环(一个人只能抢一次,so没必要循环)
synchronized (obj) {
// 同步代码块
if (packetCount == 0) {
// 判断,共享数据是否到了末尾(已经到了末尾)
System.out.println(getName() + "没有抢到红包!");
}else {
// 判断,共享数据是否到了末尾(没有到末尾)
// 定义一个变量,表示中奖的金额
double prize = 0;
if (packetCount == 1) {
// 此时只剩下最后一个红包
// 最值范围:【0.01, (100 - 第一个 - 第二个)】
// 无需随机,剩余的钱都是中间金额
prize = money;
}else {
// 表示第一次,第二次(随机)
Random r = new Random();
// 100元 3个红包
// 第一个红包的最值范围:【0.01,99.98】
// 99.98 = 100 - (3-1) * 0.01
double bounds = money - (packetCount - 1) * MIN_MONEY;
prize = r.nextDouble(bounds);
if (prize < MIN_MONEY) {
// 如果小于0.01,则算做0.01
prize = MIN_MONEY;
}
}
// 从money当中,去掉当前中奖的金额
money = money - prize;
// 红包的个数 - 1
packetCount--;
System.out.println(getName() + "抢到了" + prize + "元。");
}
}
}
}
public class MyTest {
public static void main(String[] args) {
RedPacket rp1 = new RedPacket();
RedPacket rp2 = new RedPacket();
RedPacket rp3 = new RedPacket();
RedPacket rp4 = new RedPacket();
RedPacket rp5 = new RedPacket();
rp1.setName("用户1");
rp2.setName("用户2");
rp3.setName("用户3");
rp4.setName("用户4");
rp5.setName("用户5");
rp1.start();
rp2.start();
rp3.start();
rp4.start();
rp5.start();
}
}
用BigDecimal实现
使用BigDecimal实现可以解决精度问题,不过阿伟老师的如下给出的BigDecimal其实依旧没有解决精度问题,开发中禁止使用将double直接转为BigDecimal
应该先转为字符串,再去处理,底层使用数组存储每一位,进行位运算,即可避免精度损失
public class RedPacket extends Thread{
// 总金额100元
static BigDecimal money = BigDecimal.valueOf(100.0);
// 分成三个红包
static int packetCount = 3;
// 最小的中奖金额:0.01元
static final BigDecimal MIN_MONEY = BigDecimal.valueOf(0.01);
static Object obj = new Object();
@Override
public void run() {
// 循环(一个人只能抢一次,so没必要循环)
synchronized (obj) {
// 同步代码块
if (packetCount == 0) {
// 判断,共享数据是否到了末尾(已经到了末尾)
System.out.println(getName() + "没有抢到红包!");
}else {
// 判断,共享数据是否到了末尾(没有到末尾)
// 定义一个变量,表示中奖的金额
BigDecimal prize;
if (packetCount == 1) {
// 此时只剩下最后一个红包
// 最值范围:【0.01, (100 - 第一个 - 第二个)】
// 无需随机,剩余的钱都是中间金额
prize = money;
}else {
// 表示第一次,第二次(随机)
Random r = new Random();
// 100元 3个红包
// 第一个红包的最值范围:【0.01,99.98】
// 99.98 = 100 - (3-1) * 0.01
// double bounds = money - (packetCount - 1) * MIN_MONEY;
double bounds = money.subtract(BigDecimal.valueOf(packetCount - 1).multiply(MIN_MONEY)).doubleValue();
// prize = r.nextDouble(bounds);
prize = BigDecimal.valueOf(r.nextDouble(bounds));
}
//设置抽中红包,小数点保留两位,四舍五入
prize = prize.setScale(2, RoundingMode.HALF_UP);
// 从money当中,去掉当前中奖的金额
// money = money - prize;
money = money.subtract(prize);
// 红包的个数 - 1
packetCount--;
System.out.println(getName() + "抢到了" + prize + "元。");
}
}
}
}
public class MyTest {
public static void main(String[] args) {
RedPacket rp1 = new RedPacket();
RedPacket rp2 = new RedPacket();
RedPacket rp3 = new RedPacket();
RedPacket rp4 = new RedPacket();
RedPacket rp5 = new RedPacket();
rp1.setName("用户1");
rp2.setName("用户2");
rp3.setName("用户3");
rp4.setName("用户4");
rp5.setName("用户5");
rp1.start();
rp2.start();
rp3.start();
rp4.start();
rp5.start();
}
}
第五题
第六题
第七题
第八题
线程池
线程不释放,线程复用
发现复用了线程1
常见方法
两个Java自带的工具类
具体实现
public class MyThreadPool {
public static void main(String[] args) {
// 1.获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
// ExecutorService pool2 = Executors.newFixedThreadPool(3);
// 2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
// 3.销毁线程池 (一般不会销毁)
// pool1.shutdown();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
自定义线程池
Java自带的工具类Excutors不够灵活
ThreadPoolExecutor实现类
查看Executors源码,其中一个实现类ThreadPoolExecutor的构造器有7个参数
理解七个参数
线程池处理逻辑(重要)
核心线程:3
临时线程:3
阻塞队列长度:3
此时提交了10个任务
会发生什么事情呢?
- 首先核心线程1~3立即处理任务1~3
- 其次创建阻塞队列让任务4~6等待,等着核心线程1~3来处理
- 然后创建临时线程4~6立即处理任务7~9
- 3(核心线程) + 3(临时线程)+ 3(队列长度) < 10,则第10个任务被拒绝服务,除法任务拒绝策略