0%

今天在做LeetCode27题(https://leetcode.cn/problems/remove-element/)时发现了一个问题,我自己的解法如下:

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
/**
* Created with IntelliJ IDEA.
*
* @Author: smionf
* @Date: 2024/04/19/16:40
* @Description:
*/
public class LC27RemoveElement {
public static int removeElement(int[] nums, int val) {
int[] newNums = new int[nums.length];
int count = 0,j=0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == val) {
count++;
continue;
}
newNums[j]=nums[i];
j++;
}
nums = newNums;

return nums.length-count;
}

public static void main(String[] args) {
int[] nums = {3,2,2,3};
int lg = removeElement(nums, 3);
System.out.println(lg);
System.out.println("------------");
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
}
}

以上代码的打印结果是[3,2,2,3]而不是[2,2,0,0],没有通过debug发现原因。

查询资料(参考链接:java 值传递 数组传递 - The_PopCore - 博客园 (cnblogs.com))发现原因如下:

以上代码中,虽然在removeElement方法中将符合条件的元素替换为0,但是在main方法中打印数组时,打印的仍然是原始的nums数组,而没有打印替换后的nums数组。这是因为Java中的数组是按值传递的,所以在removeElement方法中修改nums数组并不会影响main方法中的nums数组。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test{

String str = new String("good");
char[] ch = {'a','b','c'};
int i = 10;
public void change(String str,char ch,int i){

str = "test ok";
ch = 'g';
this.i = i+1;
}

public static void main(String[] args){

Test tt = new Test();
tt.change(tt.str,tt.ch[0],tt.i);
System.out.println(tt.i);
System.out.print(tt.str+" and ");
System.out.println(tt.ch);
}
}

change()方法里的入参为char ch;

传递的是个char值的单个数组元素,此时ch=’g’;是不影响源数组元素的。

this.i = i+1;这里面等号左边的i是属性i,等号右边的i是局部变量(入参里的i);

此时i+1后赋值给属性的i,自然会改变属性i的值,同时17行,tt.i又是调用属性的i,输出的结果是:

1
2
3
11

good and abc

顺带附上LeetCode27题我的解法:

题目:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

这道题可以用双指针解法(快指针寻找新数组的元素 ,新数组就是不含有目标元素的数组,慢指针指向更新 新数组下标的位置):

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 com.simonf.array;

/**
* Created with IntelliJ IDEA.
*
* @Author: smionf
* @Date: 2024/04/19/16:40
* @Description:
*/
public class LC27RemoveElement {
public static int removeElement(int[] nums, int val) {
int slowIndex = 0;//新数值元素的更新位置
int fastIndex ;
for (fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] == val) {
continue;
}
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
return slowIndex;
}

public static void main(String[] args) {
int[] nums = {3, 2, 2, 3};
int lg = removeElement(nums, 3);
System.out.println(lg);
System.out.println("------------");
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
}


}

note1

用位运算代替乘除法提高运算效率,例如:

1
2
3
4
5
6
System.out.println(8>>1); //4 相当于8%(2^1)
System.out.println(8>>2); //2 相当于8%(2^2)
System.out.println(8>>3); //1 相当于8%(2^3)
System.out.println(2<<1); //4 相当于2*(2^1)
System.out.println(2<<2); //8 相当于2*(2^2)
System.out.println(2<<3); //16 相当于2*(2^3)

如果想在service层通过request获取请求报文中的信息,可以通过RequestContextHolder得到request对象。

用法:

1
2
3
4
5
6
7
8
//  获取请求对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 转化为ServletRequestAttributes
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
// 获取到HttpServletRequest 对象
HttpServletRequest request = sra.getRequest();
// 获取到HttpServletResponse 对象
HttpServletResponse response = sra.getResponse();

request 和 response 如何与RequestContextHolder进行挂钩的?看底层源码

首先分析RequestContextHolder这个类,里面有两个ThreadLocal保存当前线程下的request

1
2
3
4
5
public abstract class RequestContextHolder {
// 得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
//可被子线程继承的reques
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");

再看getRequestAttributes() 方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.

1
2
3
4
5
6
7
8
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}

request和response等是什么时候设置进去的?

springMVC 核心类DispatcherServlet 继承关系:DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet

  1. HttpServletBean 进行初始化工作
  2. FrameworkServlet 初始化 WebApplicationContext,并提供service方法预处理请
  3. DispatcherServlet 具体分发处理.

那么就可以在FrameworkServlet查看到该类重写了service(),doGet(),doPost()…等方法,这些实现里面都有一个预处理方法processRequest(request, response);,所以定位到了我们要找的位置

查看processRequest(request, response);的实现,具体可以分为以下几步:

  1. 获取上一个请求的参数
  2. 重新建立新的参数
  3. 设置到XXContextHolder
  4. 父类的service()处理请求
  5. 恢复request(恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
  6. 发布事件(发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息)

使用MinIO上传图片时报错:

1
io.minio.errors.ErrorResponseException: The difference between the request time and the server's time is too large.

这可能是因为linux服务器时间与windows时间不一致造成的。

在linux服务器里同步时间,方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
第一步:安装ntp服务
yum -y install ntp
第二步:开启开机启动服务
systemctl enable ntpd
第三步:启动服务 Tips:联网正常前提下如果定时同步失败,先停止服务,再启动
systemctl stop ntpd
systemctl start ntpd
第四步:更改时区
timedatectl set-timezone Asia/Shanghai
第五步:启用ntp同步
timedatectl set-ntp yes
第六步:同步时间
ntpq -p

先上定义:

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

在此先引入MySQL中锁的分类:

其中,独占锁是写锁,可理解为传统意义上的读写锁,读读不互斥,读写互斥,写写互斥。

排他锁用法:

1
SELECT ... FROM UPDATE;

排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。

for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。

验证排他锁,设计一张表:

1
2
3
4
5
6
7
8
9
10
11
12
/* 开启事务1 */
BEGIN;
/* 查询name为张三的数据并加上排他锁 */
SELECT * FROM test WHERE name = '张三' FOR UPDATE;
/* 延迟10秒执行 */
SELECT SLEEP(10);
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 5000 WHERE name = '张三';
/* 延迟15秒执行 */
SELECT SLEEP(15);
/* 提交事务1 */
COMMIT;

开启事务1的执行过程如下:

同时开启事务二:

1
2
3
4
5
6
/* 开启事务2 */
BEGIN;
/* 普通查询name = '张三'的数据 */
SELECT * FROM test WHERE name = '张三';
/* 提交事务2 */
COMMIT;

事务二很快执行完成了,执行结果如下:

可以看出在事务一开启了排他锁以后,其他事务(事务二)仍然可以进行读操作,并产生了不可重复读的结果(在事务一没提交之前,事务一修改张三的余额为5000,事务二读取的张三的余额却为7000,待事务一执行完毕后,事务二读取的张三的余额变为5000)。

如果事务一不变的情况下

1
2
3
4
5
6
7
8
9
10
11
12
/* 开启事务1 */
BEGIN;
/* 查询name为张三的数据并加上排他锁 */
SELECT * FROM test WHERE name = '张三' FOR UPDATE;
/* 延迟10秒执行 */
SELECT SLEEP(10);
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 5000 WHERE name = '张三';
/* 延迟15秒执行 */
SELECT SLEEP(15);
/* 提交事务1 */
COMMIT;

事务二对张三的余额进行修改操作

1
2
3
4
5
6
/* 开启事务2 */
BEGIN;
/* 尝试修改 name = '张三' 的数据 */
UPDATE test SET balance = 2000 WHERE name = '张三';
/* 提交事务2 */
COMMIT;

可以看到在事务一开启排他锁的期间,事务二无法进行修改操作,等待20多秒事务一提交后才执行了事务二。事务二在事务一后执行,所以张三的余额被修改为2000。


————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/FENGBOKE/article/details/136166260

Nacos有时候虽然可以正常启动,但是一访问http://127.0.0.1:8848/进程就会stopped。查看日志有这么一句报错:

1
ErrMsg:Nacos Server did not start because dumpservice bean construction failure :No DataSource set

这有可能是数据库的连接没有释放导致的,可以尝试在数据库中

1
flush hosts

再尝试连接

然后可以查询下远程权限是否开放

1
select host,user from user

如果没有开放

1
update user set host='%' where user='root'

开放远程权限再尝试启动Nacos,这时再访问Nacos问题得以解决。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/FENGBOKE/article/details/136579894

今天遇到一个很有意思的问题:Nacos工作的好好的,一运行程序,nacos就自动stopped了。

首先日志排查,linux命令:

1
docker logs nacos

初步怀疑是nacos启动后资源不足,但是日志里没有OutOfMemoryError等错误,但是日志里有这么一句:

1
2
3
2024-03-08 22:07:25,713 ERROR Application run failed

Nacos Server did not start because dumpservice bean construction failure : No DataSource set

百度了一下发现是mysql配置文件出了问题,mysql8.0之后的版本, 需要在官网文档配置mysql的基础上加上mysql时区的设置。

像这样:

1
2
3
4
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db_spzx?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true

运行程序后报了新的错误:

1
Caused by: com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING

依旧是百度了一下,发现nocos不仅需要配置中心地址,还得配置服务注册中心的地址。像这样:

1
2
3
4
5
6
7
8
9
spring:
application:
name: simonf
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848

加入了服务注册中心的地址重启项目,运行成功!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/FENGBOKE/article/details/136573527

创建新文章

在根目录下打开命令行,使用如下命令创建新文章:

1
hexo new <title>

如下图所示:

md文档顶部自动生成的内容称之为Front-matter,即前置信息,用于给 Hexo 渲染该 md 文档,有很多的配置项可以自己添加:

如何添加配置项:

部署到GitHub

本地运行命令:

1
hexo s

部署命令

1
2
3
hexo clean
hexo g
hexo d

当我们实现BaseMapper的时候,MP就会帮我们把BaseMapper里的接口类全部实现代理,成为可以直接被我们调用的类,这个过程完全不需要我们去写xml,当然我们也可以在接口中写上我们自己自定义的类,但是我们自定义的类必须去写xml去映射方法,默认他会去扫描resources下的mapper文件夹下的xml,类名要于接口名一样哦这个和mybatis是一样的,其实唯一的区别就是有一些类他帮我们写了其他的和mybatis没有区别。

接下来就是说IService接口和ServiceImpl,其实这两个类的存在也是很合理的,你想MP帮我们去实现了mapper接口,那mapper接口是不是得有Service接口和一个Service实现类啊?所以他其实帮我们连实现类也写好了,我们只需要去实现接口和继承实现类就好了。当然Service接口中我们也可以写自己的自定义方法但是同时我们也需要在ServiceImpl中自己去写我们自定义的方法,而从IService接口中实现的方法呢?ServiceImpl其实已经帮我们实现了,我们只要继承他就可以了。

在多说两句,继承ServiceImpl要放入两个泛型呢,第一个其实是我们之前写的那个UserMapper,
为什么要传入这个呢,看下面的图:

可以看到系统提供的这个ServiceImpl类中有一个成员变量,他被@Autowired注解修饰也就是说这个成员变量是自动注入的,可以发现他的名字叫baseMapper,懂了吧,我们那个UserMapper是继承自baseMapper的吧,所以我们把那个接口传进来,其实就是让这个实现类去自动注入我们的那个UserMapper.

第二个泛型是我们的那个model类,也就是User,我们告诉ServiceImpl我们要处理的User类,ServiceImpl再去调用UserMapper告诉Usermapper我要你处理的User,这说明什么呢,Usermapper的泛型和ServiceImpl的泛型要一致哦,不能说我Usermapper处理User类,然后你给ServiceImpl传入一个Student类,那到时候ServiceImpl传一个Student给UserMapper,UserMapper肯定就处理不了。可以看下图:

画红线的地方也看出来, ServiceImpl明确要求你这个泛型M必须继承的是一个泛型为T的BaseMapper,而T就是我们传入的第二个泛型,所以很明显Usermapper的泛型和ServiceImpl传入的第二个泛型是要一致的哦,一定要注意,虽然很基础但是往往也容易出错嘛..
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_44869121/article/details/124297999

最近看到牛客网一个帖子”面试官让我手撕一个线程池,二十分钟没手撕出来面试流程就终止了”。忽然意识到自己也手撕不出来线程池,遂加急补习了一下JUC和线程池相关的知识。下面做个全面的总结:

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,ExecutorService,ThreadPoolExecutor这几个类。

Executor接口是顶层接口,只有一个execute方法,过于简单。通常不使用它,而是使用ExecutorService接口:

那么问题来了,怎么创建一个线程池对象呢?通常使用Executors工具类

面试题:execute和submit的区别

  1. execute是Executor接口的方法,而submit是ExecutorService的方法,并且ExecutorService接口继承了Executor接口。
  2. execute只接受Runnable参数,没有返回值;而submit可以接受Runnable参数和Callable参数,并且返回了Future对象,可以进行任务取消、获取任务结果、判断任务是否执行完毕/取消等操作。
  3. 通过execute方法提交的任务无法获取具体的异常信息;而submit方法可以通过Future对象获取异常信息。

Executors工具类

直接编码演示:每种线程池的效果

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 com.atguigu.demojuc.chap08;

public class ThreadPoolDemo {

public static void main(String[] args) {


//1、基本使用
ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单一线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(3); //固定大小线程池

//可以在线程中设置一下睡眠时间控制承受并发的能力(调整睡眠时间长短看效果)
//ExecutorService threadPool = Executors.newCachedThreadPool(); // 无限大小线程池(遇强则强)

try {
for (int i = 0; i < 1000; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "开始执行业务逻辑");
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "结束");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}

定时任务:延迟执行

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 com.atguigu.jucdemo.chap08;

public class ScheduledThreadPoolDemo {

public static void main(String[] args) {

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
System.out.println(new Date());

try {

for (int i = 0; i < 10; i++) {
//延迟执行
scheduledThreadPool.schedule(()->{
System.out.println(Thread.currentThread().getName() + " 定时任务被执行" + new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 5, TimeUnit.SECONDS);
}

} finally {
scheduledThreadPool.shutdown();
}
}
}

定时任务:延迟并间隔执行

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 com.atguigu.jucdemo.chap08;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledAtFixRateDemo {

public static void main(String[] args) {

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
System.out.println(new Date());

//try {

for (int i = 0; i < 10; i++) {
//延迟执行
scheduledThreadPool.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName() + " 定时任务被执行" + new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 5, 1, TimeUnit.SECONDS);
}

/*} finally {
scheduledThreadPool.shutdown(); //不要销毁这个对象,因为任务会一直执行
}*/
}
}

底层原理

线程池的7个重要参数

  1. corePoolSize:线程池中的常驻核心线程数
  2. maximumPoolSize:线程池中能够容纳同时 执行的最大线程数,此值必须大于等于1
  3. keepAliveTime:多余的空闲线程的存活时间 当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到 只剩下corePoolSize个线程为止
  4. unit:keepAliveTime的单位
  5. workQueue:任务队列,被提交但尚未被执行的任务
  6. threadFactory:表示生成线程池中工作线程的线程工厂, 用于创建线程,一般默认的即可
  7. handler:拒绝策略,表示当队列满了,并且工作线程等于线程池的最大线程数(maximumPoolSize)时,如何来拒绝 请求执行的runnable的策略

线程池底层工作原理

具体流程:

  1. 在创建了线程池后,线程池中的线程数为零

  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
    3. 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
    4. 如果队列满了且正在运行的线程数量等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:

    • 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
    • 线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

拒绝策略

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况

ThreadPoolExecutor自带的拒绝策略如下:

  1. AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  2. CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中,尝试再次提交当前任务。
  4. DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。 如果允许任务丢失,这是最好的一种策略。

以上内置的策略均实现了RejectedExecutionHandler接口,也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略

自定义线程池

​ 在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

自定义线程池:

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
47
48
49
50
51
package com.atguigu.demojuc.chap08;

class MyRunnable implements Runnable{

private int param;

public MyRunnable(int param) {
this.param = param;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Runnable......" + param);
}
}

public class CustomizeThreadPoolDemo {

public static void main(String[] args) {

// 自定义连接池
ExecutorService threadPool = new ThreadPoolExecutor(2, 5,
2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() //丢弃任务并抛出异常
//new ThreadPoolExecutor.CallerRunsPolicy() //由调用线程处理该任务,谁调用谁处理
//new ThreadPoolExecutor.DiscardOldestPolicy() //丢弃队列中等待最久的任务,添加新任务
//new ThreadPoolExecutor.DiscardPolicy() //也是丢弃任务,但是不抛出异常。
/*new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义拒绝策略");
}
}*/
);

try {
for (int i = 0; i < 9; i++) {
threadPool.execute(() -> {
//threadPool.submit(() -> { //这里也可以使用submit
System.out.println(Thread.currentThread().getName() + "执行了业务逻辑");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}

}
}