线程池

1.Java常见的四种线程池

线程池原理:ThreadPoolExecutor里面使用到JUC同步器框架AbstractQueuedSynchronizer(俗称AQS)、大量的位操作、CAS操作。ThreadPoolExecutor提供了固定活跃线程(核心线程)、额外的线程(线程池容量 - 核心线程数这部分额外创建的线程,下面称为非核心线程)、任务队列以及拒绝策略这几个重要的功能。

1.1 newFixedThreadPool(int poolSize)

创建一个大小为poolSize的固定线程池,线程池是无边界阻塞队列。代码如下

ExecutorService exec = Executors.newFixedThreadPool(poolSize);

//它实际上是创建了一个ThreadPoolExecutor实例
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
//第一个参数是核心线程数;第二个参数是最大线程数;0L是长整型,代表当线程数量大于核心数时,在终止前,额外线程等待新任务的最大时间;第四个参数代表第三个参数的时间单位,这里是毫秒,第五个参数代表创建一个无边界的队列。
 

1.2 newSingleThreadExecutor()

创建一个单线程池,类似于newFixedThreadPool(1)
ExecutorService exec = Executors.newSingleThreadExecutor();
//它创建了一个被FinalizableDelegatedExecutorService修饰的ThreadPoolExecutor实例,所以它只有ThreadPoolExecutor的部分功能,且会被GC回收
new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(11, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
 
 

1.3 newCachedThreadPool()

创建一个具有缓冲功能的线程池

ExecutorService exec = Executors.newCachedThreadPool();

//它创建了ThreadPoolExecutor实例,它是一个同步队列
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
//这会创建一个无界的线程池,线程可以空闲的最大时间是60秒,在这60秒都是已创建的线程都是可以复用的。
 

1.4 newScheduledThreadPool(int poolSize)

创建一个具有计划执行的固定大小线程池

ExecutorService exec = Executors.newScheduledThreadPool(poolSize)

//它创建了ThreadPoolExecutor实例,但是使用的是优先级队列
new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())
 

2.ThreadPoolExecutor

由于使用上面的线程池或多或少有些限制,所以使用底层的ThreadPoolExecutor可以自定义线程池的属性。

2.1 参数详解

第一个参数corePoolSize意思是核心线程大小;线程池中保留的线程数,即使是空闲状态也保留,除非设置了允许核心线程超时,默认是不设置,即不销毁核心线程

第二个参数是最大线程数;

第三个是keepAliveTime保留时间,当最大线程数大于核心线程数时,多余的线程保留时间,超过这个时间多余的线程将被销毁。

第四个是第三个时间的时间单位,是属于TimeUnit的枚举值。

第五个是一个阻塞队列,在任务提交执行前,任务都是这个阻塞队列的内容。

第六个是线程工厂,这种工厂模式会被用于创建新线程。

第七个是拒绝策略,当线程池线程达到最大值,阻塞队列排满时使用这个参数,,默认是抛弃执行超出的任务,直接抛异常。

2.2 阻塞队列

2.2.1 ArrayBlockingQueue

基于数组的有界阻塞队列,规则是FIFO(先进先出)。

2.2.2 LinkedBlockingQUeue

基于链表的无界阻塞队列,规则也是FIFO,如果不设定大小,默认是无界的,此时可能会造成内存溢出等问题。

2.2.3 SychronousQueue

同步的阻塞队列,它不存储元素,上一个元素执行完才能执行下一个元素。

2.3 拒绝策略

ThreadPoolExecutor类有4个拒绝策略的内部类。默认的拒绝策略是AbortPolicy。

2.3.1 AbortPolicy

线程池达到最大线程数且阻塞队列满,直接抛出异常

2.3.2 DiscardPolicy

线程池达到最大线程数且阻塞队列满,遗弃这个任务

2.3.3 DiscardOldestPolicy

线程池达到最大线程数且阻塞队列满,遗弃最旧的任务,尝试将新任务添加到新线程池

2.3.4 CallerRunPolicy

线程池达到最大线程数且阻塞队列满,尝试使用主线程执行该任务

2.4 线程池工作原理

2.4.1 原理

第一步,线程池判断核心线程池是否都在执行任务,若不是,则创建线程来执行任务;若是,则执行第二步

第二步,使用阻塞队列来存储任务,若阻塞队列已满,则执行第三步。

第三步,线程池判断现在是否达到最大线程数,若不是,创建线程来执行任务,若是,则执行饱和策略。

 参数如何设置:

我们要根据任务是 计算密集型 or I/O密集型 来设置线程池的大小  看开发人员对cpu或io操作比例大小吧。计算密集型是指处理这种任务时,线程不会发生阻塞,线程不阻塞就一定程度代表 CPU 一直在忙碌;I/O 密集型 是指运行该类任务时线程多会发生阻塞,一旦阻塞,CPU 就多被闲置,浪费 CPU 资源。
对于计算密集型的任务,在有N 个处理器的系统上,当线程池的大小为 N+1 时,能实现 CPU 的最优利用率。(即使当计算密集型的线程 偶尔由于页缺失故障或者其他原因暂停时,这个“额外” 的线程也能确保CPU 的时装周期不会被浪费。)
对于 I/O 操作或其他 阻塞任务,由于线程并不会一直执行,因此线程池的规模应该更大 2N大小

线程池基本原理:

实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行




 //代码  submit 和execute区别   submit 可以返回值  也可以用get 等待当前所有子线程完成。


package cn.shenzhen.nk.Thread.pool;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class InvokeAll {
private static Logger log = LogManager.getLogger(InvokeAll.class);

// public static int addNum = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
test();
}

public static void test() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
List<Future> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Future future = threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}

}
});
futures.add(future);
}
for (Future future : futures
) {
future.get();

}
System.out.println("主线程已经执行完了");


threadPoolExecutor.shutdown();
try {
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("线程池没有正常关闭");
}
}
} catch (InterruptedException e) {
threadPoolExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
//当线程池shotdown时候,线程池不再接收任何新任务,但此时线程池并不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出,调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了
//这种无限循环我觉得还是别用
while (true) {
if (threadPoolExecutor.isTerminated()) {
System.out.println("所有的子线程都结束了!");
Long end = System.currentTimeMillis();
System.out.println(end - start);
break;
}
}


}

}
 
原文地址:https://www.cnblogs.com/ningkuan/p/13864130.html