一、背景:java.util.concurrent.ThreadPoolExecutor
是ExecutorSerivce接口的具体实现。ThreadPoolExecutor使用线程池中的一个线程来执行给定的任务(Runnable或者Runnable)。是用来处理异步任务的一个接口,可以将其理解成为一个线程池和一个任务队列,提交到 ExecutorService 对象的任务会被放入任务队或者直接被线程池中的线程执行。
继承关系:
public interface Executor
public interface ExecutorService extends Executor
AbstractExecutorService implements ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
二、介绍:
1、构造函数与参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
构造函数的参数含义如下:
(1)corePoolSize:核心线程池容量大小。线程组保留的最小线程数,如果线程组中的线程数少于此数目,则创建。指定了线程池中的线程数量,决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;线程进行核心线程池即可等待调度执行;
(2)maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。通过这个来判断线程池是否已满。MaximumPoolSize = CorePoolSize + WorkQueue + 临时线程池大小
(3)workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
(4)keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
(5)unit:keepAliveTime的单位,为枚举,配合线程存活时间使用;
(6)threadFactory:线程工厂,用于创建线程,一般用默认即可;
(7)handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
2、ThreadPoolExecutor池子的处理流程: 当一个线程进入线程池之后,会进行如下的处理步骤:
(1)首先查看核心线程池是否满,当运行的线程数小于corePoolSize的时候 ,创建新的线程即Worker执行提交的任务
(2)如果线程数大于等于corePoolSize的时候,将任务提交到workQueue队列中 ,如果成功添加 ,runWorker就会执行调用了,当然这里会重新的核查此时的线程数,看下是否有线程减少,如果减少,则创建新的线程来使线程数维持在corePoolSize的数目
(3)如果队列满了后,则创建新的线程来执行,当然这里有一种极端情况,当线程数等于maximumPoolSize时,并且workQueue也满了后,就会抛出错 误 org.springframework.core.task.TaskRejectedException,另外MaxPoolSize的设定如果比系统支持的线程数还要大时,会抛出java.lang.OutOfMemoryError: unable to create new native thread 异常。
三、重要参数详解:
1、workQueue任务队列:
(1)直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。希望提交的任务尽快分配线程执行建议使用此。例:
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo {
private static ThreadPoolExecutor pool;
public static void main( String[] args )
{
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new SynchronousQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.execute(new DemoTask());
}
}
}
package exceldemo;
public class DemoTask implements Runnable{
public DemoTask() {
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
输出结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@63961c42 rejected from java.util.concurrent.ThreadPoolExecutor@65b54208[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at exceldemo.Demo.main(Demo.java:15)
pool-1-thread-1
pool-1-thread-2
可以看到,当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。
即,使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
(2) 有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现:
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.execute(new DemoTask());
}
输出结果:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
如果任务数过多:
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<13;i++) {
pool.execute(new DemoTask());
}
也会报错:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@5674cd4d rejected from java.util.concurrent.ThreadPoolExecutor@63961c42[Running, pool size = 2, active threads = 2, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at exceldemo.Demo.main(Demo.java:20)
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于等于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
(3) 无界的任务队列:可以使用LinkedBlockingQueue实现,对于需要保证所有提交的任务都要被执行的情况建议使用此
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
pool = new ThreadPoolExecutor(1,
2,
1000,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<13;i++) {
pool.execute(new DemoTask());
}
输出结果:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
任务数过多也不会报错。
使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。
(4)优先任务队列:优先任务队列通过PriorityBlockingQueue实现,
package exceldemo;
public class DemoTask implements Runnable,Comparable{
private int priority;
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public DemoTask() {
}
public DemoTask(int priority) {
this.priority = priority;
}
//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高
@Override
public int compareTo(DemoTask o) {
return this.priority>o.priority?-1:1;
}
@Override
public void run() {
try {
//让线程阻塞,使后续任务进入缓存队列
Thread.sleep(1000);
System.out.println("priority:"+this.priority+",ThreadName:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
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 )
{
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
//优先任务队列
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask(i));
}
}
}
执行结果:
priority:0,ThreadName:pool-1-thread-1
priority:9,ThreadName:pool-1-thread-1
priority:8,ThreadName:pool-1-thread-1
priority:7,ThreadName:pool-1-thread-1
priority:6,ThreadName:pool-1-thread-1
priority:5,ThreadName:pool-1-thread-1
priority:4,ThreadName:pool-1-thread-1
priority:3,ThreadName:pool-1-thread-1
priority:2,ThreadName:pool-1-thread-1
priority:1,ThreadName:pool-1-thread-1
可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
2、拒绝策略
package exceldemo;
public class DemoTask implements Runnable{
public DemoTask() {
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,就需要你指定合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略及举例如下:
(1)抛出异常AbortPolicy策略(默认策略):该策略会直接抛出异常RejectedExecutionException
,阻止系统正常工作;
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(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task exceldemo.DemoTask@5674cd4d rejected from java.util.concurrent.ThreadPoolExecutor@63961c42[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
pool-1-thread-2
pool-1-thread-1
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
pool-1-thread-2
pool-1-thread-1
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
pool-1-thread-2
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
pool-1-thread-2
at exceldemo.Demo.main(Demo.java:19)
pool-1-thread-1
(2)由调用线程处理CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
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(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
main
main
main
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
(3)丢弃最旧任务DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
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(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
(4)丢弃任务DiscardPolicy策略:直接丢弃任务,但是不抛出异常。该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
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(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
注:以上内置的策略均实现了RejectedExecutionHandler接口,当然也可以自己扩展RejectedExecutionHandler接口,自定义拒绝策略,如:
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(5),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
for(int i=0;i<10;i++) {
pool.execute(new DemoTask());
}
}
}
结果:
exceldemo.DemoTask@5674cd4d执行了拒绝策略
exceldemo.DemoTask@63961c42执行了拒绝策略
exceldemo.DemoTask@65b54208执行了拒绝策略
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
3、 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(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
4、ThreadPoolExecutor扩展 :ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个接口实现的,
beforeExecute:线程池中任务运行前执行
afterExecute:线程池中任务运行完毕后执行
terminated:线程池退出后执行
举例:
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(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
线程池退出
5、线程池数量:
线程吃线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可
/**
* Nthreads=CPU数量
* Ucpu=目标CPU的使用率,0<=Ucpu<=1
* W/C=任务等待时间与任务计算时间的比率
*/
Nthreads = Ncpu*Ucpu*(1+W/C)
6、提交任务:
(1)无返回值的任务使用public void execute(Runnable command) 方法提交,子线程可能在主线程结束之后结束;
(2)有返回值的任务使用public 提交,因为提交任务后有个取数据的过程,在从
Future取数据的过程中,Callable自带的阻塞机制,这个机制保证主线程一定在子线程结束之后结束。反之如果没有取数据,子线程可能会在主线程结束之后才结束。
详见下下一篇博客。
三、应用规范:
1、整个项目中只初始化一次ThreadPoolExecutor;
2、线程与主线程(调用处)是异步执行的,需要注意参数传递的正确性,
举例如下:如用户表有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 getByIds(List ids) {
List 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 getAll( ){
List ids = new ArrayList<>();
for(int i = 0;i<=500;i++){
ids.add(i);
}
long startTime = new Date().getTime();
List users = new ArrayList<>();
List 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(),
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> {
private List userIds;
private UserService userService;
public UserTask(List userIds,UserService userService) {
this.userIds = userIds;
this.userService = userService;
}
@Override
public List call() throws Exception {
List 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 getAll( ){
List ids = new ArrayList<>();
for(int i = 0;i<=500;i++){
ids.add(i);
}
long startTime = new Date().getTime();
//List users = userService.getByIds(ids);
//多线程处理开始*******************************************
List 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> future = MyThreadPoolExecutor.getThreadPoolInstance().submit(task);
futures.add(future);
}
//取数据
List users = new ArrayList<>();
try{
for (Future futureIter : futures) {
List personnelDetailDOS = (List) 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> future = excutor.submit(task);
futures.add(future);
queryIds.clear();
}
}
if(queryIds.size() > 0){
UserTask task = new UserTask(queryIds,userService);
Future> 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
2)如果call在queryIds.clear之后,且在下一个childIds.add(id)之前被调度执行,这时call方法里面拿到的对象List
......
而第一种写法给每个UserTask传递的参数就是一个List,且这个List在它所在的UserTask生命周期中没有被改变,所以结果是正确的。由此可见,在异步操作时要注意参数的一致性。
当然,第二种种批量写法这样改造也是正确的:
ThreadPoolExecutor excutor = MyThreadPoolExecutor.getThreadPoolInstance();
for(Integer id : ids){
queryIds.add(id);
if(queryIds.size() == 100){
List copyIds = new ArrayList<>();
for(Integer copyId : queryIds){
copyIds.add(copyId);
}
UserTask task = new UserTask(copyIds,userService);
Future> future = excutor.submit(task);
futures.add(future);
queryIds.clear();
}
}
if(queryIds.size() > 0){
UserTask task = new UserTask(queryIds,userService);
Future> future = excutor.submit(task);
futures.add(future);
}
execute提交保证线程安全:
List users = userMapper.selectAll();
List> userPageList= Lists.partition(users,100);
for(int page = 0;page < userPageList.size();page++){
int finalPage = page;
threadPoolTaskExecutor.execute(()->{
List statPersons = userPageList.get(finalPage);
//逻辑
});
}