一、线程池配置
@Configuration
@EnableAsync
public class ExecutorConfig {
private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
@Value("${async.executor.thread.core_pool_size}")
private int corePoolSize;
@Value("${async.executor.thread.max_pool_size}")
private int maxPoolSize;
@Value("${async.executor.thread.queue_capacity}")
private int queueCapacity;
@Value("${async.executor.thread.name.prefix}")
private String namePrefix;
@Bean(name = "asyncServiceExecutor")
public Executor asyncServiceExecutor() {
logger.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(corePoolSize);
//配置最大线程数
executor.setMaxPoolSize(maxPoolSize);
//配置队列大小
executor.setQueueCapacity(queueCapacity);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix(namePrefix);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
applications.properties中配置@Value("${}")的值
# 异步线程配置
# 配置核心线程数
async.executor.thread.core_pool_size = 20
# 配置最大线程数
async.executor.thread.max_pool_size = 20
# 配置队列大小
async.executor.thread.queue_capacity =9999999
# 配置线程池中的线程的名称前缀
async.executor.thread.name.prefix = async-service-
二、编写测试类
@Test
@Order(2)
public void insertMany(){
int num=20;
try {
//计数器数量就等于线程数量
countDownLatch = new CountDownLatch(num);
for(int i=0;i<num;i++){
iAsyncService.executeAsyncInsertUser(countDownLatch,i);
}
//主线程唤醒
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("end");
}
}
三、service层
public interface IAsyncService {
public void executeAsyncInsertUser(CountDownLatch countDownLatch, int i);
}
@Service
public class AsyncServiceImpl implements IAsyncService {
@Override
@Async("asyncServiceExecutor")
public void executeAsyncInsertUser(CountDownLatch countDownLatch,int i){
Connection con = null;
PreparedStatement pstmt = null;
try {
long start = System.currentTimeMillis();
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test_a?setUnicode=true&characterEncoding=utf8&useSSL=false", "***", "***");
System.out.println("线程" + Thread.currentThread().getId() + "开始执行");
for (int j=1;j<=5000;j++){
String name= NameUtil.getChineseName();
int age= NumberUtil.getNum(0,100);
String sex= SexUtil.getSex();
String address= ProvinceUtil.getProvince();
pstmt = con.prepareStatement("insert into users(name,age,sex,address) values(?,?,?,?)");
pstmt.setString(1,name);
pstmt.setInt(2,age);
pstmt.setString(3,sex);
pstmt.setString(4,address);
pstmt.execute();
if (j%100==0){
System.out.println("线程" + Thread.currentThread().getId() + ", j="+j);
}
}
long end = System.currentTimeMillis();
//计算本线程运行完毕使用的时间,单位为秒
long threadUsedTimeSecond=(end-start)/1000;
System.out.println("线程" + Thread.currentThread().getId() + "执行结束,用时"+threadUsedTimeSecond+"s");
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
try {
if(pstmt != null) pstmt.close();
if(con != null) con.close();
} catch (Exception e) {
System.err.println(e.getMessage());
}
//线程运行完毕,线程计数器-1
countDownLatch.countDown();
}
}
}
四、执行时间
下表为不同线程数完成插入10万条数据所用时间,本机cup为八核
线程数 | 时间(秒) |
20 | 381 |
30 | 253 |
40 | 193 |
50 | 157 |
100 | 84 |
200 | 44+ |
500 | 22 |
1000 | 16/17/18 |
900 | 16/17/18 |
800 | 17/18/19 |
在线程数为200时,在插入9.5w+数据后,程序报错:
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"
应该是MySQL的默认max_connections属性配置太小,重新配置了MySQL的my.ini设置max_connections=1000,
重启MySQL后,跑500线程数,跟预期结果一样正常运行,并且时间大幅缩短;
1000线程数跑完了但也出现Too many connections错误,但可以看出增加到1000后相比500收益没有很明显。
实际运行的线程数应该小于max_connections。
可以看出来,增加线程数对于大量数据的插入会起到非常大的收益,大幅减少运行时间。