创建线程池的方式
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
- ForkJoinPool
ThreadPoolExecutor
- 案例 多线程处理大量数据
CountDownLatch 来使主线程等待线程池中的线程执行完毕。
package com.example.demo.threadpool;
import cn.hutool.core.io.file.FileReader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 多线程处理大量数据
* @author rp
*/
@RestController
@RequestMapping("/vast")
public class VastDataController {
private static List mysqlData;
private static CountDownLatch threadsSignal;
/**
* 每个线程处理的数据量
* */
private static final int count = 10000;
static class ThreadNameFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, threadNumber.getAndIncrement()+"线程");
}
}
/**
* 定义线程池数量为8,每个线程处理1000条数据
* */
private static ThreadPoolExecutor execPool = new ThreadPoolExecutor(8, 8,
5, TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadNameFactory());
/**
* 多线程模拟处理大量数据,模拟请求
* */
@RequestMapping("/dataHandle")
public static String dataHandle(){
mysqlData = new ArrayList<>();
long start = System.currentTimeMillis();
batchAddData();
long end = System.currentTimeMillis();
System.out.println("==========");
String s = end - start + "ms";
System.out.println(s);
mysqlData = null;
return s;
}
/**
* 多线程批量执行插入,百万数据需要大约不到20秒 64位4核处理
*/
public static void batchAddData() {
//需要插入数据库的数据
List limodel = readFile();
try {
if(limodel.size() <= count) {
threadsSignal = new CountDownLatch(1);
execPool.submit(new InsertDate(limodel));
}else {
List> li = createList(limodel, count);
threadsSignal = new CountDownLatch(li.size());
for(List liop : li) {
execPool.submit(new InsertDate(liop));
}
}
threadsSignal.await();
} catch (Exception e) {
e.printStackTrace();
}
limodel = null;
}
/**
* 数据拆分
* @param targe
* @param size
* @return
*/
private static List> createList(List targe, int size) {
List> listArr = new ArrayList<>();
//获取被拆分的数组个数
int arrSize = targe.size() % size == 0 ? targe.size() / size : targe.size() / size + 1;
for(int i = 0 ; i < arrSize ; i++) {
List sub = new ArrayList();
//把指定索引数据放入到list中
for(int j = i*size ; j <= size*(i+1)-1 ; j++) {
if(j <= targe.size()-1) {
sub.add(targe.get(j));
}
}
listArr.add(sub);
}
return listArr;
}
/**
* 内部类,开启线程批量保存数据
* @author rp
*
*/
static class InsertDate extends Thread{
List lienties = new ArrayList<>();
InsertDate(List listModel){
//可对数据进行定制化处理/计算等
lienties.addAll(listModel);
}
@Override
public void run() {
//模拟存入数据库
mysqlData.addAll(lienties);
threadsSignal.countDown();
}
}
public static List readFile(){
//默认UTF-8编码,可以在构造中传入第二个参数做为编码,vast.txt 测试数据文件
FileReader fileReader = new FileReader("/Users/****/develop/***/interview/src/main/resources/vast.txt");
String[] split = fileReader.readString().split(",");
return Arrays.asList(split);
}
}
ScheduledThreadPoolExecutor
package com.example.demo.threadpool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池ScheduledThreadPoolExecutor详解
* @author rp
*/
@Slf4j
public class ScheduledThreadPoolExecutorTest {
static class ThreadNameFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(5);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, threadNumber.getAndIncrement()+"线程");
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
5,
new ThreadNameFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
/**
* 延迟3秒执行任务
* */
executor.schedule(
new Runnable() {
@Override
public void run() {
log.info("延迟3秒后执行~~~");
}
},
3,
TimeUnit.SECONDS
);
/**
* 延迟5秒后执行
* */
ScheduledFuture schedule = executor.schedule(
new Callable() {
@Override
public String call() throws Exception {
return "获得执行结果";
}
},
5,
TimeUnit.SECONDS
);
log.info(schedule.get());
/**
* scheduleAtFixedRate
* */
executor.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
log.info("执行任务");
}
},
// 第一次执行任务延迟多久
0,
// 每隔多久执行一次任务
3,
TimeUnit.SECONDS
);
/**
* scheduleWithFixedDelay
* */
executor.scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
log.info("执行任务");
}
},
// 第一次执行任务延迟多久
0,
// 每次执行完任务之后,延迟多久再次执行这个任务
3,
TimeUnit.SECONDS
);
}
}
ForkJoinPool
package com.example.demo.threadpool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* ForkJoinPool 实现1-100的求和
* ForkJoinPool 实际中不怎么使用
* @author rp
*/
@Slf4j
public class ForkJoinPoolTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask task = forkJoinPool.submit(new Task(1, 1000));
Integer integer = task.get();
log.info("求和结果:{}",integer);
}
static class Task extends RecursiveTask {
/**
* 当前任务计算的起始
*/
private Integer start;
/**
* 当前任务计算的结束
*/
private Integer end;
/**
* 阈值,end-start在阈值以内,那么就不用再去细分任务
*/
private static final int threshold = 2;
public Integer getStart() {
return start;
}
public void setStart(Integer start) {
this.start = start;
}
public Integer getEnd() {
return end;
}
public void setEnd(Integer end) {
this.end = end;
}
public Task(Integer start, Integer end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
boolean needFork = (end - start) > threshold;
if (needFork){
int middle = (end + start)/2;
Task leftTask = new Task(start, middle);
Task rightTask = new Task(middle+1, end);
//执行子任务
leftTask.fork();
rightTask.fork();
//子任务执行完成之后的结果
Integer leftJoin = leftTask.join();
Integer rightJoin = rightTask.join();
sum = leftJoin+rightJoin;
}else {
for (int i = start;i <= end;i++){
sum += i;
}
}
return sum;
}
}
}
线程池调优
调优工具类
package com.example.demo.threadpool.tuning;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A class that calculates the optimal thread pool boundaries. It takes the desired target utilization and the desired
* work queue memory consumption as input and retuns thread count and work queue capacity.
*
* @author Niklas Schlimm
*/
public abstract class PoolSizeCalculator {
/**
* The sample queue size to calculate the size of a single {@link Runnable} element.
*/
private final int SAMPLE_QUEUE_SIZE = 1000;
/**
* Accuracy of test run. It must finish within 20ms of the testTime otherwise we retry the test. This could be
* configurable.
*/
private final int EPSYLON = 20;
/**
* Control variable for the CPU time investigation.
*/
private volatile boolean expired;
/**
* Time (millis) of the test run in the CPU time calculation.
*/
private final long testtime = 3000;
/**
* Calculates the boundaries of a thread pool for a given {@link Runnable}.
*
* @param targetUtilization the desired utilization of the CPUs (0 <= targetUtilization <= 1)
* @param targetQueueSizeBytes the desired maximum work queue size of the thread pool (bytes)
*/
protected void calculateBoundaries(BigDecimal targetUtilization, BigDecimal targetQueueSizeBytes) {
calculateOptimalCapacity(targetQueueSizeBytes);
Runnable task = creatTask();
start(task);
start(task); // warm up phase
long cputime = getCurrentThreadCPUTime();
start(task); // test intervall
cputime = getCurrentThreadCPUTime() - cputime;
long waittime = (testtime * 1000000) - cputime;
calculateOptimalThreadCount(cputime, waittime, targetUtilization);
}
private void calculateOptimalCapacity(BigDecimal targetQueueSizeBytes) {
long mem = calculateMemoryUsage();
BigDecimal queueCapacity = targetQueueSizeBytes.divide(new BigDecimal(mem), RoundingMode.HALF_UP);
System.out.println("Target queue memory usage (bytes): " + targetQueueSizeBytes);
System.out.println("createTask() produced " + creatTask().getClass().getName() + " which took " + mem
+ " bytes in a queue");
System.out.println("Formula: " + targetQueueSizeBytes + " / " + mem);
System.out.println("* Recommended queue capacity (bytes): " + queueCapacity);
}
/**
* Brian Goetz' optimal thread count formula, see 'Java Concurrency in Practice' (chapter 8.2)
*
* @param cpu cpu time consumed by considered task
* @param wait wait time of considered task
* @param targetUtilization target utilization of the system
*/
private void calculateOptimalThreadCount(long cpu, long wait, BigDecimal targetUtilization) {
BigDecimal waitTime = new BigDecimal(wait);
BigDecimal computeTime = new BigDecimal(cpu);
BigDecimal numberOfCPU = new BigDecimal(Runtime.getRuntime().availableProcessors());
BigDecimal optimalthreadcount = numberOfCPU.multiply(targetUtilization).multiply(
new BigDecimal(1).add(waitTime.divide(computeTime, RoundingMode.HALF_UP)));
System.out.println("Number of CPU: " + numberOfCPU);
System.out.println("Target utilization: " + targetUtilization);
System.out.println("Elapsed time (nanos): " + (testtime * 1000000));
System.out.println("Compute time (nanos): " + cpu);
System.out.println("Wait time (nanos): " + wait);
System.out.println("Formula: " + numberOfCPU + " * " + targetUtilization + " * (1 + " + waitTime + " / "
+ computeTime + ")");
System.out.println("* Optimal thread count: " + optimalthreadcount);
}
static class ThreadNameFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, threadNumber.getAndIncrement() + "线程");
}
}
/**
* Runs the {@link Runnable} over a period defined in {@link #testtime}. Based on Heinz Kabbutz' ideas
* (http://www.javaspecialists.eu/archive/Issue124.html).
*
* @param task the runnable under investigation
*/
public void start(Runnable task) {
long start = 0;
int runs = 0;
do {
if (++runs > 5) {
throw new IllegalStateException("Test not accurate");
}
expired = false;
start = System.currentTimeMillis();
// Timer timer = new Timer();
// timer.schedule(new TimerTask() {
// @Override
// public void run() {
// expired = true;
// }
// }, testtime);
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
1,
new ThreadNameFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
executor.schedule(
new Runnable() {
@Override
public void run() {
expired = true;
}
},
testtime,
TimeUnit.MILLISECONDS
);
while (!expired) {
task.run();
}
start = System.currentTimeMillis() - start;
executor.shutdown();
// timer.cancel();
} while (Math.abs(start - testtime) > EPSYLON);
collectGarbage(3);
}
private void collectGarbage(int times) {
for (int i = 0; i < times; i++) {
System.gc();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
/**
* Calculates the memory usage of a single element in a work queue. Based on Heinz Kabbutz' ideas
* (http://www.javaspecialists.eu/archive/Issue029.html).
*
* @return memory usage of a single {@link Runnable} element in the thread pools work queue
*/
public long calculateMemoryUsage() {
BlockingQueue queue = createWorkQueue();
for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
queue.add(creatTask());
}
long mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
queue = null;
collectGarbage(15);
mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
queue = createWorkQueue();
for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
queue.add(creatTask());
}
collectGarbage(15);
mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
return (mem1 - mem0) / SAMPLE_QUEUE_SIZE;
}
/**
* Create your runnable task here.
*
* @return an instance of your runnable task under investigation
*/
protected abstract Runnable creatTask();
/**
* Return an instance of the queue used in the thread pool.
*
* @return queue instance
*/
protected abstract BlockingQueue createWorkQueue();
/**
* Calculate current cpu time. Various frameworks may be used here, depending on the operating system in use. (e.g.
* http://www.hyperic.com/products/sigar). The more accurate the CPU time measurement, the more accurate the results
* for thread count boundaries.
*
* @return current cpu time of current thread
*/
protected abstract long getCurrentThreadCPUTime();
}
调优案例
package com.example.demo.threadpool.tuning;
import lombok.extern.slf4j.Slf4j;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 线程池调优示例
* 线程数调优
* 1. CPU密集型任务 N+1
* 2. IO密集型任务 2N
* 3. 混合型任务
* N * ∪ * (1+WT/ST)
* ●N: CPU核心数
* ●U: 目标CPU利用率
* ●WT: 线程等待时间
* ●ST: 线程运行时间
* @author rp
*/
@Slf4j
public class MyPoolSizeCalculator extends PoolSizeCalculator {
/**
* 运行结果将打印出应设置相关调优参数数值
* */
public static void main(String[] args) {
MyPoolSizeCalculator calculator = new MyPoolSizeCalculator();
calculator.calculateBoundaries(
//CPU目标利用率
new BigDecimal(1.0),
// BlockingQueue 占用的内存大小,byte
new BigDecimal(100000));
}
@Override
protected long getCurrentThreadCPUTime() {
//当前线程占用的总时间
return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
}
/**
* 实际项目中需要运行的任务
* */
@Override
protected Runnable creatTask() {
return new Runnable() {
@Override
public void run() {
//log.info("实际项目中需要运行的任务");
}
};
}
/**
* 计算BlockingQueue大小
* */
@Override
protected BlockingQueue createWorkQueue() {
return new LinkedBlockingQueue<>();
}
}
运行结果
- Recommended queue capacity (bytes): 2500
对应队列数 new LinkedBlockingQueue<>(2500)
- Formula: 12 * 1 * (1 + 9672000 / 2990328000)
对应公式 N * ∪ * (1+WT/ST)
- Optimal thread count: 12
对应线程数 corePoolSize=12,maximumPoolSize=12