1、简单的线程池创建Executors
1.1 newCachedThreadPool()
public static ExecutorService newCachedThreadPool()
说明:创建一个线程池。需要使用线程时从线程池中获取线程,如果无可用线程,则创建一个线程,在使用使用后放入线程池。线程池中60秒未使用的线程将被终止并从缓存中移除。
因此通过该方法创建的线程池,长时间不适用,将不怎么消耗资源。
该线程池适合用于执行短暂异步任务
代码示例:
package com.liyong.reactor.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleNewCachedThreadPool {
static class Worker implements Runnable {
@Override
public void run() {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " exe success");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
Thread.sleep(10);
executor.submit(new Worker());
}
executor.shutdown();
}
}
运行结果:
pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success
说明:每个任务执行30ms,每10ms提交一个任务,所以部分线程得已复用。
1.2 newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads)
说明:该方法创建一个固定线程数量、无界等待队列的线程池,在任何时候最多只有nThread个线程在处理任务。当所有线程都被占用时,任务将放到等待队列中,等待队列无限大。池中的线程永久有效,直到明确的关闭线程池为止。
该线程池可用于可控的任务提交和任务执行,否则在大量任务堆积时,会造成OOM,而且已经提交的任务就丢失了。
代码实例:
package com.liyong.reactor.thread;
import java.time.LocalTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleNewFixedThreadPool {
static class Worker implements Runnable {
private final int index;
public Worker(int index) {
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalTime.now() + " index:" + index + " " + Thread.currentThread().getName() + " exe success");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
Thread.sleep(10);
System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
executor.submit(new Worker(i + 1));
}
executor.shutdown();
}
}
运行结果:
16:54:58.823 thread index: 1 submit success
16:54:58.835 thread index: 2 submit success
16:54:58.845 thread index: 3 submit success
16:54:58.855 thread index: 4 submit success
16:54:58.865 thread index: 5 submit success
16:54:58.875 thread index: 6 submit success
16:54:58.885 thread index: 7 submit success
16:54:58.895 thread index: 8 submit success
16:54:58.905 thread index: 9 submit success
16:54:58.915 thread index: 10 submit success
16:54:59.125 index:1 pool-1-thread-1 exe success
16:54:59.135 index:2 pool-1-thread-2 exe success
16:54:59.145 index:3 pool-1-thread-3 exe success
16:54:59.155 index:4 pool-1-thread-4 exe success
16:54:59.425 index:5 pool-1-thread-1 exe success
16:54:59.435 index:6 pool-1-thread-2 exe success
16:54:59.445 index:7 pool-1-thread-3 exe success
16:54:59.455 index:8 pool-1-thread-4 exe success
16:54:59.725 index:9 pool-1-thread-1 exe success
16:54:59.735 index:10 pool-1-thread-2 exe success
说明:设置了固定的线程池大小为4,每个线程执行300ms,每隔10ms提交一个任务到线程池中。
从输出结果 ,可以看到有且仅有4个线程能执行任务,剩下的任务都在进行排队。比如 index为10的任务,16:54:58.915提交任务,16:54:59.735完成任务,总共耗时820ms,执行时间为300ms,在队列中等待了520ms的时间。
1.3 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()
说明:创建一个单线程,无界队列的线程池。任务保证顺序执行,随时都有且仅有一个线程在执行任务。
适合场景:适合可控任务数量,可控执行时间的任务使用,并且提供了任务的顺序执行。
代码示例:
package com.liyong.reactor.thread;
import java.time.LocalTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleNewSingleThreadPool {
static class Worker implements Runnable {
private final int index;
public Worker(int index) {
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalTime.now() + " index:" + index + " " + Thread.currentThread().getName() + " exe success");
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
Thread.sleep(10);
System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
executor.submit(new Worker(i + 1));
}
executor.shutdown();
}
}
执行结果:
17:05:52.656 thread index: 1 submit success
17:05:52.667 thread index: 2 submit success
17:05:52.677 thread index: 3 submit success
17:05:52.687 thread index: 4 submit success
17:05:52.697 thread index: 5 submit success
17:05:52.757 index:1 pool-1-thread-1 exe success
17:05:52.857 index:2 pool-1-thread-1 exe success
17:05:52.957 index:3 pool-1-thread-1 exe success
17:05:53.057 index:4 pool-1-thread-1 exe success
17:05:53.157 index:5 pool-1-thread-1 exe success
说明: 任务执行耗时100ms,没10ms提交一个任务,总共提交5个任务,由结果的index序号观察,任务是顺序执行的,并且仅有一个线程在处理任务,其他未处理的任务再排队等待。index=5的任务,从提交任务到处理完成,总耗时460ms,等待了360ms的时间
1.4、newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
说明:创建一个线程池,可以指定线程数,排队队列无界,可以调度命令在给定的延迟之后运行,或定期执行。
代码示例:
package com.liyong.reactor.thread;
import java.io.IOException;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SimpleNewScheduledThreadPool {
static class Worker implements Runnable {
private final int index;
public Worker(int index) {
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(LocalTime.now() + " index:" + index + " " + Thread.currentThread().getName() + " exe success");
}
}
public static void main(String[] args) throws InterruptedException, IOException {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
System.out.println(LocalTime.now() + " thread index: " + 1111 + " submit success");
executor.schedule(new Worker(1111), 5, TimeUnit.SECONDS);
System.out.println(LocalTime.now() + " thread index: " + 2222 + " submit success");
executor.scheduleWithFixedDelay(new Worker(2222), 6000, 200, TimeUnit.MILLISECONDS);
// System.out.println(LocalTime.now() + " thread index: " + 3333 + " submit success");
// executor.scheduleAtFixedRate(new Worker(3333), 6000, 200, TimeUnit.MILLISECONDS);
System.in.read();
executor.shutdown();
}
}
输出结果:
17:37:23.877 thread index: 1111 submit success
17:37:23.878 thread index: 2222 submit success
17:37:29.179 index:1111 pool-1-thread-1 exe success
17:37:30.179 index:2222 pool-1-thread-2 exe success
17:37:30.680 index:2222 pool-1-thread-1 exe success
17:37:31.181 index:2222 pool-1-thread-2 exe success
17:37:31.682 index:2222 pool-1-thread-3 exe success
17:37:32.183 index:2222 pool-1-thread-1 exe success
17:37:32.684 index:2222 pool-1-thread-4 exe success
17:37:33.185 index:2222 pool-1-thread-2 exe success
17:37:33.686 index:2222 pool-1-thread-2 exe success
说明: 任务执行300ms,index 1111的任务5s后开始执行;index 2222的任务6000ms后开始执行,延时200ms后再次执行;index 3333的任务6000ms后开始执行,没间隔200ms后开始执行,间隔是以该任务上次执行结束时开始计算。
public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit);
指定在多久后开始执行,仅执行一次
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);指定多久后开始执行,每个delay的时长后,再次执行,从输出日志可以看到,该调度器在存在足够的线程数的情况下,该任务会存在多个线程同时在执行。
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);指定多久后开始执行,每个执行周期为period,period是从该任务上次结束后开始统计,所以即使线程数足够的清康熙,该任务只有一个线程在执行
1.5 newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
说明:该方法与1.4中的Executors.newScheduledThreadPool(1)效果一样
2、直接使用ThreadPoolExecutor类创建线程池,不使用Executors创建线程池
package com.liyong.reactor.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CustomThreadPoolExecutor {
public static void main(String[] args) {
// 丢弃老的任务
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
// 将抛出RejectedExecutionException异常
new ThreadPoolExecutor.AbortPolicy();
// 调用者自己执行任务
new ThreadPoolExecutor.CallerRunsPolicy();
// 丢弃任务
new ThreadPoolExecutor.DiscardPolicy();
/*
* int corePoolSize 核心线程数
* int maximumPoolSize 池中允许的最大线程数
* long keepAliveTime 当线程数大于核心时,这个空闲线程将等待多长时间闲置后被回收
* TimeUnit unit 闲置时间单位
* BlockingQueue workQueue 任务队列,主要用于设置任务队列深度
* RejectedExecutionHandler handler 任务拒绝策略
*/
new ThreadPoolExecutor(8, 64, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(10), handler);
}
}
说明: 使用Executors创建的线程池,除了调度线程池外,其他线程池底层都是通过ThreadPoolExecutor来进行构造的。通过自己构造线程池,可以对线程池的各个细节进行设置。
关于拒绝策略:java提供了4中拒绝策略
// 丢弃老的任务
new ThreadPoolExecutor.DiscardOldestPolicy();
// 将抛出RejectedExecutionException异常
new ThreadPoolExecutor.AbortPolicy();
// 调用者自己执行任务
new ThreadPoolExecutor.CallerRunsPolicy();
// 丢弃任务
new ThreadPoolExecutor.DiscardPolicy();但是这4中拒绝策略在我们真正的业务中,其实实用性不是太高。往往可以通过自定义进行优化
优化方案:
从上面的线程池配置中,线程数的配置其实是比较关键的,它直接影响了我们程序运行的效率。那怎么设置一个合适的线程池大小呢?
将被处理的任务分为: CPU密集型的、IO密集型的
如果是CPU密集型的任务,那么就是需要“长时间”占用CPU进行运算的,线程数尽量小,比如配置:CPU 个数 +1 的线程数。
如果是IO密集型的任务,因为IO操作时是不暂用CPU的,尽量不要让CPU闲下来,应尽可能大的增加线程数,比如配置: CPU 个数 * 2 +1。
Runtime.getRuntime().availableProcessors()
可以通过该方法获取当前机器的可用处理器个数。
如果任务对其他资源有依赖,比如从数据库查询数据,从远处服务器拉取数据。等待数据资源时间越长,CPU空闲的时间就越长,所以增加线程数是更好利用CPU的好方法。
因此给出一个估算的公式:
最佳线程数目 = (( 线程等待时间 + 线程 CPU 时间 ) / 线程 CPU 时间 ) * CPU 数目
将公式进一步化简,得到:
最佳线程数目 = ( 线程等待时间与线程CPU 时间之比+1 ) * CPU 数目
因此得到结论:线程等待时间所占比例越高,需要越多线程。线程 CPU 时间所占比例越高,需要越少线程。
自定义决绝策略:
package com.liyong.reactor.thread;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//TODO 写日志
//TODO 发MQ消息
//TODO 发管理员提醒
//TODO executor.getQueue().put(r);
}
}
说明:此处只是一段伪代码,主要为了说明在自定义拒绝策略时,可以向系统管理员,以及相关负责人发出警告,这样可以快速进行人工干预,比如:加机器、代码BUG定位等相关操作。
总结:
Java 多线程开发优化有两个思路: