Java并发编程——四种线程池的使用及分析

2022-12-02 09:37:49 浏览数 (1)

执行一个异步任务你还只是new Thread吗? 那你就out太多了,new Thread的弊端如下:

a. 每次new Thread新建对象性能差。

b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。

c.缺乏更多功能,如定时执行、定期执行、线程中断。

用线程池吧,少年

相比new Thread,Java提供的四种线程池的好处

a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。

b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。

c. 提供定时执行、定期执行、单线程、并发数控制等功能。

Java提供的四种线程池

  1. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  2. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  3. newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  4. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

了解之后,就让我们来使用一下 代码示例:

1.newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

代码语言:javascript复制
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i  ) {
final int index = i;
fixedThreadPool.execute(new Runnable() {

@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。可参考PreloadDataCache。

2.newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

代码语言:javascript复制
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i  ) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {

@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}

结果依次输出,相当于顺序执行各个任务。

3.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

代码语言:javascript复制
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i  ) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

cachedThreadPool.execute(new Runnable() {

@Override
public void run() {
System.out.println(index);
}
});
}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

4.newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。 延迟执行示例代码如下:

代码语言:javascript复制
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {

@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);

表示延迟3秒执行。

定期执行示例代码如下:

代码语言:javascript复制
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);

表示延迟1秒后每3秒执行一次。

分析

那光会使用怎么能行,我们还要进去看看它的原理: 点进去创建固定线程池的方法,

原来是调了一个方法,把线程数量当参数传了进去 换一个看看,再点进去缓存线程池

又调用了这个方法,看来ThreadPoolExecutor()这个方法才是创建线程池的真正的方法,让我们再点进去这个方法 打开Structure,看到这个方法有四个重载,传递的不同参数

这里给出参数是什么意思,供大家参考

代码语言:javascript复制
public ThreadPoo1Executor(
int corePool size,//核心线程数量
int maximumPoolsize,/最大线程数
long keepAliveTime,//超时时间,超出核心线程数量以外的线程空余存活时间
Timeunit unit,//存活时间单位
BlockingQueue<Runnable> workQueue,//保存执行任务的队列
ThreadFactory threadFactory,//创建新线程使用的工厂
RejectedExecutionHandler handler//当任务无法执行的时候的处理方式
)

这里大家注意一下这两个参数

代码语言:javascript复制
int corePool size,//核心线程数量
int maximumPoolsize,/最大线程数

其实这就是有编制的和临时工的区别,什么意思呢 就是如果我们公司某个项目赶得紧,人手不够了,怎么办?扩招吗,可是平时又用不到这么多人,那我们可以找外包公司招点外包人员嘛,临时借过来等项目完成了再还回去。 即核心到最大 之间的这部分,是可变的,需要就有,不需要就没有。

0 人点赞