1.在常量接口里面定义好线程池参数
public interface StaticConstantEnum {
/**
* 线程池配置
*/
interface threadPollArgs {
//核心线程数
int CorePoolSize = 4;
//最大线程数
int MaxPoolSize = 4;
//等待队列
int QueueCapacity = 12;
//非核心线程数空闲存活时间
int KeepAliveSeconds = 3;
//核心线程数空闲超时回收
boolean AllowCoreThreadTimeOut = true;
//等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
int AwaitTerminationSeconds = 60;
//等待任务在关机时完成--表明等待所有线程执行完
boolean WaitForTasksToCompleteOnShutdown = true;
//配置线程池中的线程的名称前缀
String ThreadNamePrefix = "workThread-";
}
}
2选择生成线程池的方法
2.1 (Spring)
//获取线程池
public ThreadPoolTaskExecutor asyncExecutorPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(CorePoolSize);
//配置最大线程数
executor.setMaxPoolSize(MaxPoolSize);
//配置队列大小
executor.setQueueCapacity(QueueCapacity);
//等待任务在关机时完成--表明等待所有线程执行完
executor.setWaitForTasksToCompleteOnShutdown(WaitForTasksToCompleteOnShutdown);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
executor.setAwaitTerminationSeconds(AwaitTerminationSeconds);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix(ThreadNamePrefix);
//非核心线程数空闲存活时间
executor.setKeepAliveSeconds(KeepAliveSeconds);
//核心线程数空闲超时是否回收
executor.setAllowCoreThreadTimeOut(AllowCoreThreadTimeOut);
/*rejection-policy:当pool已经达到max size的时候,
如何处理新任务(CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行)*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化执行器
executor.initialize();
return executor;
}
2.2原生方法(非Spring)
//获取线程池
public static ThreadPoolExecutor asyncExecutorPool() {
LongAdder a = new LongAdder();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CorePoolSize, //核心
MaxPoolSize, //最大
KeepAliveSeconds, //存活
TimeUnit.MILLISECONDS, //存活单位毫秒
new LinkedBlockingQueue(QueueCapacity), //队列
//修改线程前缀
r -> {
a.add(1L);
return new Thread(r, ThreadNamePrefix+a.sum());
},
new ThreadPoolExecutor.CallerRunsPolicy()//拒绝策略
);
return executor;
}
3.把原来的任务 job 放到线程池中去,如下
应用时可以直接复制 jobByMultiThread() 和 前面获取线程池的方法。然后把自己的任务替换到 job();那里即可。
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.LongAdder;
import static com.lzy.stu.ftpdownload.com.constant.StaticConstantEnum.threadPollArgs.*;
public class TestThreadPool {
//多线程方法
public void jobByMultiThread() throws InterruptedException {
//调用方法获得设置好的线程池
ThreadPoolTaskExecutor threadPoolTaskExecutor = asyncExecutorPool();
//ThreadPoolExecutor threadPoolTaskExecutor =
asyncExecutorPool(); //非Spring下
//工作数量
int jobNum = 10;
//信号量控制主线程等待线程池把任务做完
CountDownLatch countDownLatch = new CountDownLatch(jobNum);
//统计任务开始时间
long start = System.currentTimeMillis();
for (int i = 0; i < jobNum; i++) {
//将任务放到线程池
threadPoolTaskExecutor.execute(() -> {
try {
//要改为多线程的任务
job();
} finally {
//任务完成信号量减少
countDownLatch.countDown();
}
});
}
//信号量不为零就等待
countDownLatch.await();
//关闭线程池
threadPoolTaskExecutor.shutdown();
//统计任务结束时间
long end = System.currentTimeMillis();
//打印任务用时
System.out.println("多线程计时:" + miliToSecond(start,end)
+",任务需要总时间:" + miliToSecond(0,jobTotalTime.sum())
);
}
//毫秒转秒
private BigDecimal miliToSecond(long start, long end) {
return BigDecimal.valueOf(end - start)
.divide(BigDecimal.valueOf(1000))
.setScale(2, RoundingMode.HALF_DOWN);
}
//获取线程池
public ThreadPoolTaskExecutor asyncExecutorPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(CorePoolSize);
//配置最大线程数
executor.setMaxPoolSize(MaxPoolSize);
//配置队列大小
executor.setQueueCapacity(QueueCapacity);
//等待任务在关机时完成--表明等待所有线程执行完
executor.setWaitForTasksToCompleteOnShutdown(WaitForTasksToCompleteOnShutdown);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
executor.setAwaitTerminationSeconds(AwaitTerminationSeconds);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix(ThreadNamePrefix);
//非核心线程数空闲存活时间
executor.setKeepAliveSeconds(KeepAliveSeconds);
//核心线程数空闲超时是否回收
executor.setAllowCoreThreadTimeOut(AllowCoreThreadTimeOut);
/*rejection-policy:当pool已经达到max size的时候,
如何处理新任务(CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行)*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化执行器
executor.initialize();
return executor;
}
//要改为多线程的任务
public void job() {
try {
//模拟本次任务执行随即时间 0.5~1.5 秒
long t = 500L + new Random().nextInt(1000);
System.out.println("t = " + t);
//统计任务总用时
jobTotalTime.add(t);
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程
Thread thread = Thread.currentThread();
//打印当前线程名字
System.out.println(thread.getName() + "\tworking...");
}
//统计job总用时
private final LongAdder jobTotalTime = new LongAdder();
}
4.测试:
@Test
public void testThreadPoll() throws InterruptedException {
new TestThreadPool().jobByMultiThread();
}
可以看到多线程用时更少。
5.线程安全
单线程改为多线程一定注意线程安全问题,建议测试的时候将最大线程数调整到100(尽量大),这样容易暴露程序中的线程安全问题,从而去解决它。建议学习JUC相关内容。
下面给出几个常用的线程安全解决CV素材
5.1 ReentrantLock 可重入锁
可重入简单理解递归调用的时候也是安全的,他的用法大概如下:
//可重入互斥锁
private final ReentrantLock lock = new ReentrantLock();
//要改为多线程的任务
public void job() {
//上锁
lock.lock();
try {
//任务内容
}finally {
//解锁
lock.unlock();
}
}
我们把前面的 job 从头到尾锁一下,这样就和原来单线程差不多,但是多了线程调度,所以用时更多了。当然可以根据自己的判断去锁一下代码块,放大缩小都相当灵活好用。
//要改为多线程的任务
public void job() {
//上锁
lock.lock();
try {
try {
//模拟本次任务执行随即时间 0.5~1.5 秒
long t = 500L + new Random().nextInt(1000);
System.out.println("t = " + t);
//统计任务总用时
jobTotalTime.add(t);
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程
Thread thread = Thread.currentThread();
//打印当前线程名字
System.out.println(thread.getName() + "\tworking...");
}finally {
//解锁
lock.unlock();
}
}
再执行发现多线程10.04,任务共需9.94。
5.2 ThreadLocal 线程本地变量
简单理解就是,把公用的东西变为一人一份。
举个栗子,我们有个章:(*^▽^*),每个线程进来看这个章很可爱就占为己有,把自己的名字刻在章后面。代码如下
//可爱的章
private String data = "(*^▽^*)";
//job2
public void job2() {
String tName = Thread.currentThread().getName();//workThread_?
data += tName+" ";
System.out.println("data = " + data);
}
然后再主线程打印一下章,可以看到可爱的章已经面目全非。
然后我们改为 ThreadLocal 并初始化一下章,通过get(),set()获取、设置变量如下:
//带初始化的 ThreadLocal 每个线程有自己的一份
private static final ThreadLocal<String> data = ThreadLocal.withInitial(() -> "(*^▽^*)");
public void job2() {
String tName = Thread.currentThread().getName();//workThread_?
tName+=" | "; //workThread_? |
data.set(data.get() + tName);
System.out.println("data = " + data.get());
}
可以发现每个线程抖在自己的章上疯狂输出,主线程的章依然冰清玉洁。
注意,但是上面的用法存在内存泄漏风险,因为其机制存在弱引用,这里不详解感兴趣自学。需要在每个线程任务结束的时候(finaly那里)调用一下ThreadLocal的remove()来清除线程变量,如下data.remove():
public void jobByMultiThread() throws InterruptedException {
//调用方法获得设置好的线程池
ThreadPoolExecutor threadPoolTaskExecutor = asyncExecutorPool();
//工作数量
int jobNum = 10;
//信号量控制主线程等待线程池把任务做完
CountDownLatch countDownLatch = new CountDownLatch(jobNum);
for (int i = 0; i < jobNum; i++) {
//将任务放到线程池
threadPoolTaskExecutor.execute(() -> {
try {
//要改为多线程的任务
job2();
} finally {
//任务完成信号量减少
countDownLatch.countDown();
//清除线程变量
data.remove();
}
});
}
//信号量不为零就等待
countDownLatch.await();
//关闭线程池
threadPoolTaskExecutor.shutdown();
//打印最后的章
System.out.println("主线程的章:" + data.get());
}
如此一来,每次都是崭新的可爱章了: