一、ThreadFactory自定义线程创建
1、自定义ThreadFactory
线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等,如:
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
//自定义线程工厂
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
new ThreadFactory() {
public Thread newThread(Runnable r) {
System.out.println("线程"+r.hashCode()+"创建");
//线程命名
Thread th = new Thread(r,"threadPool"+r.hashCode());
return th;
}
}, new ThreadPoolExecutor.CallerRunsPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
线程2061475679创建
线程140435067创建
main
main
main
threadPool2061475679
threadPool2061475679
threadPool2061475679
threadPool2061475679
threadPool2061475679
threadPool2061475679
threadPool140435067
2、ThreadPoolExecutor扩展
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的,
beforeExecute:线程池中任务运行前执行
afterExecute:线程池中任务运行完毕后执行
terminated:线程池退出后执行
demo:
package exceldemo;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
//自定义线程工厂
//实现自定义接口
pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
new ThreadFactory() {
public Thread newThread(Runnable r) {
System.out.println("线程"+r.hashCode()+"创建");
//线程命名
Thread th = new Thread(r,"threadPool"+r.hashCode());
return th;
}
},
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void beforeExecute(Thread t,Runnable r) {
System.out.println("准备执行:"+ ((DemoTask)r).getTaskName());
}
@Override
protected void afterExecute(Runnable r,Throwable t) {
System.out.println("执行完毕:"+((DemoTask)r).getTaskName());
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
for(int i=0;i<10;i++) {
pool.execute(new DemoTask("Task"+i));
}
pool.shutdown();
}
}
package exceldemo;
public class DemoTask implements Runnable{
private String taskName;
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public DemoTask(String name) {
this.setTaskName(name);
}
@Override
public void run() {
//输出执行线程的名称
System.out.println("TaskName"+this.getTaskName()+"---ThreadName:"+Thread.currentThread().getName());
}
}
输出:
执行结果:
线程2061475679创建
线程140435067创建
线程1450495309创建
准备执行:Task0
线程1670782018创建
TaskNameTask0---ThreadName:threadPool2061475679
TaskNameTask9---ThreadName:main
执行完毕:Task0
准备执行:Task2
准备执行:Task1
TaskNameTask2---ThreadName:threadPool2061475679
执行完毕:Task2
准备执行:Task3
TaskNameTask3---ThreadName:threadPool2061475679
准备执行:Task7
执行完毕:Task3
TaskNameTask7---ThreadName:threadPool1450495309
执行完毕:Task7
准备执行:Task5
准备执行:Task4
TaskNameTask5---ThreadName:threadPool1450495309
TaskNameTask4---ThreadName:threadPool2061475679
TaskNameTask1---ThreadName:threadPool140435067
执行完毕:Task5
执行完毕:Task1
准备执行:Task6
TaskNameTask6---ThreadName:threadPool1450495309
执行完毕:Task4
执行完毕:Task6
准备执行:Task8
TaskNameTask8---ThreadName:threadPool1670782018
执行完毕:Task8
线程池退出
二、集成规范
1、配置ThreadPoolExecutor
整个项目中一般只配置一个ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池配置
* 根据业务不同(有的业务执行快,有的业务执行慢),使用不同的线程池
* 自定义线程工厂
* 自定义拒绝策略,暂时用"调用者运行策略"
*/
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程数
*/
private static final int corePoolSize = 8;
/**
* 线程池中允许的最大线程数
*/
private static final int maximumPoolSize = 16;
/**
* 线程空闲超时时间(秒)
*/
private static final long keepAliveTime = 30;
/**
* 任务队列
*/
private static final int capacity = 100;
/**
* 站内信、短信-线程池
*
* @return
*/
@Bean(name = "threadPool")
public ThreadPoolExecutor msgThreadPoolExecutor() {
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(capacity),
new CustomThreadFactory("msg"),
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
如果线程应用的比较多,也可以配置多个线程池:比如
线程池1:@Bean(name="serviceJobTaskExecutor")
线程池2 :@Bean(name="msgJobTaskExecutor")
2、调用
调用处直接获取bean,再根据具体业务提交线程即可。
@Autowired
private static ThreadPoolExecutor msgJobTaskExecutor;
注意:切记子线程与主线程(调用处)是异步执行的,需要注意参数传递的正确性,
举例如下:如用户表有id、user_name、age,现在根据id集合查询用户列表,当id集合过大,使用in查询效率低,如果采用循环,单线程又比较耗时,这时可以使用多线程去处理。
package exceldemo.service.impl;
import exceldemo.dto.User;
import exceldemo.service.UserService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public List<User> getByIds(List<Integer> ids) {
List<User> users = new ArrayList<>();
for(Integer id : ids){
User user = new User();
user.setAge(id);
user.setUserName("用户"+id);
//耗时操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
users.add(user);
}
return users;
}
}
先看普通的单线程写法:
package exceldemo.rest;
import exceldemo.dto.User;
import exceldemo.service.UserService;
import exceldemo.thread.MyThreadPoolExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//获取所有用户
@RequestMapping("/getAll")
public List<User> getAll( ){
List<Integer> ids = new ArrayList<>();
for(int i = 0;i<=500;i++){
ids.add(i);
}
long startTime = new Date().getTime();
List<User> users = new ArrayList<>();
List<Integer> queryIds = new ArrayList<>();
for(Integer id : ids){
queryIds.add(id);
if(queryIds.size() == 100){
users.addAll(userService.getByIds(queryIds));
queryIds.clear();
}
}
if(queryIds.size() > 0){
users.addAll(userService.getByIds(queryIds));
}
long endTime = new Date().getTime();
System.out.println("耗时"+(endTime-startTime));
return users;
}
}
请求后后端打印:
耗时5055
多线程:
package exceldemo.thread;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolExecutor {
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
21,
1000,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());;
public static ThreadPoolExecutor getThreadPoolInstance(){
System.out.println(threadPoolExecutor);
return threadPoolExecutor;
}
}
package exceldemo.task;
import exceldemo.dto.User;
import exceldemo.service.UserService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
public class UserTask implements Callable<List<User>> {
private List<Integer> userIds;
private UserService userService;
public UserTask(List<Integer> userIds,UserService userService) {
this.userIds = userIds;
this.userService = userService;
}
@Override
public List<User> call() throws Exception {
List<User> users = userService.getByIds(userIds);
return users;
}
}
package exceldemo.rest;
import exceldemo.dto.User;
import exceldemo.service.UserService;
import exceldemo.task.UserTask;
import exceldemo.thread.MyThreadPoolExecutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//获取所有用户
@RequestMapping("/getAll")
public List<User> getAll( ){
List<Integer> ids = new ArrayList<>();
for(int i = 0;i<=500;i++){
ids.add(i);
}
long startTime = new Date().getTime();
//List<User> users = userService.getByIds(ids);
//多线程处理开始*******************************************
List<Future> futures = new ArrayList<>();
for (int i = 0; i < ids.size(); i += 100) {
int startIndex = i;
int endIndex = startIndex + 100 > ids.size() ? ids.size() : startIndex + 100;
UserTask task = new UserTask(ids.subList(startIndex, endIndex),userService);
Future<List<User>> future = MyThreadPoolExecutor.getThreadPoolInstance().submit(task);
futures.add(future);
}
//取数据
List<User> users = new ArrayList<>();
try{
for (Future futureIter : futures) {
List<User> personnelDetailDOS = (List<User>) futureIter.get();
users.addAll(personnelDetailDOS);
}
}catch (Exception e){
}
//多线程处理结束*******************************************
long endTime = new Date().getTime();
System.out.println("耗时"+(endTime-startTime));
return users;
}
}
结果:
耗时1023
获取的结果也和单线程是一样的,完全正确。
下面采用一种错误的批量写法来看下:
ThreadPoolExecutor excutor = MyThreadPoolExecutor.getThreadPoolInstance();
for(Integer id : ids){
queryIds.add(id);
if(queryIds.size() == 100){
UserTask task = new UserTask(queryIds,userService);
Future<List<User>> future = excutor.submit(task);
futures.add(future);
queryIds.clear();
}
}
if(queryIds.size() > 0){
UserTask task = new UserTask(queryIds,userService);
Future<List<User>> future = excutor.submit(task);
futures.add(future);
}
请求的结果不正确且随机:
[
{
"userName": "用户100",
"age": 100
},
{
"userName": "用户300",
"age": 300
},
{
"userName": "用户300",
"age": 300
},
{
"userName": "用户400",
"age": 400
},
{
"userName": "用户500",
"age": 500
},
{
"userName": "用户500",
"age": 500
}
]
但是在单线程中controller批量调用dubbo服务这样写是没有问题的,这是因为应用多线程时,submit(execute)方法实际调用的为Callable(Runnable)的call(run),这个call和run与主线程不是同步在执行。如在本例中传递的是一个对象,而对象是引用传递,那可能有很多种情况:
1)如果call在queryIds.clear之后,在且只在下一个childIds.add(100)之后被调度执行,这时call方法里面拿到的对象List<Integer>就是一个100了;
2)如果call在queryIds.clear之后,且在下一个childIds.add(id)之前被调度执行,这时call方法里面拿到的对象List<Integer>就是空了
......
而第一种写法给每个UserTask传递的参数就是一个List,且这个List在它所在的UserTask生命周期中没有被改变,所以结果是正确的。由此可见,在异步操作时要注意参数的一致性。
当然,第二种种批量写法这样改造也是正确的:
ThreadPoolExecutor excutor = MyThreadPoolExecutor.getThreadPoolInstance();
for(Integer id : ids){
queryIds.add(id);
if(queryIds.size() == 100){
List<Integer> copyIds = new ArrayList<>();
for(Integer copyId : queryIds){
copyIds.add(copyId);
}
UserTask task = new UserTask(copyIds,userService);
Future<List<User>> future = excutor.submit(task);
futures.add(future);
queryIds.clear();
}
}
if(queryIds.size() > 0){
UserTask task = new UserTask(queryIds,userService);
Future<List<User>> future = excutor.submit(task);
futures.add(future);
}
execute提交保证线程安全:
List<User> users = userMapper.selectAll();
List<List<User>> userPageList= Lists.partition(users,100);
for(int page = 0;page < userPageList.size();page++){
int finalPage = page;
threadPoolTaskExecutor.execute(()->{
List<User> statPersons = userPageList.get(finalPage);
//逻辑
});
}