Java 线程池
1、什么是线程池?
简单来说就是存放线程的池子,就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
线程池的优点:降低资源消耗、提高响应速度、线程可以复用、可以控制最大并发数、可以管理线程;
2、线程池提供的创建线程的方式
Executors.newSingleThreadExecutor(); 创建一个单一线程
Executors.newFixedThreadPool(int nThreads); 创建一个固定大小的线程,参数是线程的个数
Executors.newCachedThreadPool(); 创建一个可以伸缩的线程,遇强则强
3、例子如下,代码就几行,都是注释,耐心看完,动手试一下。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolMain {
/**
* 1.什么是线程池?
* 简单来说就是存放线程的池子,就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
* 线程池的优点:降低资源消耗、提高响应速度、线程可以复用、可以控制最大并发数、可以管理线程;
* <p>
* <p>
* 2.创建线程的方式
* Executors.newSingleThreadExecutor(); 创建一个单一线程
* Executors.newFixedThreadPool(int nThreads); 创建一个固定大小的线程,参数是线程的个数
* Executors.newCachedThreadPool(); 创建一个可以伸缩的线程,遇强则强
* <p>
* 例子如下
*/
public static void main(String[] args) throws InterruptedException {
// 一个一个方法调用看看 题目再控制台输出的时间长度不一样
// 没有线程
// noThreadExecutor();
// 单一的线程
// newSingleThreadExecutor();
// 创建固定线程
// newFixedThreadPool();
// 创建一个伸缩的线程池
newCachedThreadPool();
}
/**
* 没有线程
*
* @throws InterruptedException
*/
public static void noThreadExecutor() throws InterruptedException {
long startTime = System.currentTimeMillis();
long num = 0;
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
for (long j = 1; j <= 2000000000; j++) {
num += j;
}
System.out.println("最终入库的数是" + num);
num = 0;
}
// 输出当前时间减开始时间
System.out.println(System.currentTimeMillis() - startTime);
/**
* 控制台输出如下
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 最终入库的数是2000000001000000000
* 16626
*/
}
/**
* 创建一个单一的线程看看
*/
public static void newSingleThreadExecutor() {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
long startTime = System.currentTimeMillis();
try {
for (int i = 0; i < 10; i++) {
int n = i + 1;
singleThreadExecutor.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long num = 0;
for (long j = 1; j <= 2000000000; j++) {
num += j;
}
System.err.println("第" + n + "次使用名称叫" + Thread.currentThread().getName() + "线程," + "最终入库的数是:" + num);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
singleThreadExecutor.shutdown();
}
// 输出当前时间减开始时间
System.out.println(System.currentTimeMillis() - startTime);
/**
* 控制台输出如下
* 34
* 第1次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第2次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第3次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第4次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第5次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第6次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第7次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第8次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第9次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第10次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 控制台输出的都是同一个线程的名称
*/
}
/**
* 创建一个固定大小的个数
*/
public static void newFixedThreadPool() {
ExecutorService singleThreadExecutor = Executors.newFixedThreadPool(5);
long startTime = System.currentTimeMillis();
try {
for (int i = 0; i < 10; i++) {
int n = i + 1;
singleThreadExecutor.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long num = 0;
for (long j = 1; j <= 2000000000; j++) {
num += j;
}
System.err.println("第" + n + "次使用名称叫" + Thread.currentThread().getName() + "线程," + "最终入库的数是:" + num);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
singleThreadExecutor.shutdown();
}
// 输出当前时间减开始时间
System.out.println(System.currentTimeMillis() - startTime);
/**
* 控制台输出如下
* 34
* 第5次使用名称叫pool-1-thread-5线程,最终入库的数是:2000000001000000000
* 第3次使用名称叫pool-1-thread-3线程,最终入库的数是:2000000001000000000
* 第2次使用名称叫pool-1-thread-2线程,最终入库的数是:2000000001000000000
* 第4次使用名称叫pool-1-thread-4线程,最终入库的数是:2000000001000000000
* 第1次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第10次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第6次使用名称叫pool-1-thread-5线程,最终入库的数是:2000000001000000000
* 第7次使用名称叫pool-1-thread-3线程,最终入库的数是:2000000001000000000
* 第8次使用名称叫pool-1-thread-2线程,最终入库的数是:2000000001000000000
* 第9次使用名称叫pool-1-thread-4线程,最终入库的数是:2000000001000000000
* 控制台输出的线程的名称有5个
*/
}
/**
* 创建一个伸缩的线程池
*/
public static void newCachedThreadPool() {
ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();
long startTime = System.currentTimeMillis();
try {
for (int i = 0; i < 10; i++) {
int n = i + 1;
singleThreadExecutor.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long num = 0;
for (long j = 1; j <= 2000000000; j++) {
num += j;
}
System.err.println("第" + n + "次使用名称叫" + Thread.currentThread().getName() + "线程," + "最终入库的数是:" + num);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
singleThreadExecutor.shutdown();
}
// 输出当前时间减开始时间
System.out.println(System.currentTimeMillis() - startTime);
/**
* 控制台输出如下
* 34
* 第1次使用名称叫pool-1-thread-1线程,最终入库的数是:2000000001000000000
* 第9次使用名称叫pool-1-thread-9线程,最终入库的数是:2000000001000000000
* 第7次使用名称叫pool-1-thread-7线程,最终入库的数是:2000000001000000000
* 第10次使用名称叫pool-1-thread-10线程,最终入库的数是:2000000001000000000
* 第3次使用名称叫pool-1-thread-3线程,最终入库的数是:2000000001000000000
* 第8次使用名称叫pool-1-thread-8线程,最终入库的数是:2000000001000000000
* 第2次使用名称叫pool-1-thread-2线程,最终入库的数是:2000000001000000000
* 第6次使用名称叫pool-1-thread-6线程,最终入库的数是:2000000001000000000
* 第4次使用名称叫pool-1-thread-4线程,最终入库的数是:2000000001000000000
* 第5次使用名称叫pool-1-thread-5线程,最终入库的数是:2000000001000000000
* 控制台输出的线程的名称不一样
*/
}
}
上面的例子我相信你能看完了,那我们就看一下源码吧。
a、newSingleThreadExecutor 方法创建的单一线程的源码,第一个参数是1指的是核心线程数是1,第二个参数是1指的是最大线程数是1,第三个参数是0指的超时时间是0,第四个参数是TimeUnit.MILLISECONDS指的超时时间的单位是毫秒 ,第五个参数是new LinkedBlockingQueue()指的是接受任务的长度(int的最大值,假设你一秒处理1个任务,一秒进来2个任务,那么有1个任务会存在队列中,等第一个任务处理完在执行第二个任务)。
/**
创建使用单个工作线程操作的执行器从一个无限的队列中。(请注意,如果这个单一的的执行过程中失败导致线程终止关闭后,
如果需要执行,一个新的将取代它的位置后续任务。)保证任务执行,并且在任何时候活动的任务都不超过一个给定的时间。
不像其他相等的东西{@code newFixedThreadPool(1)}返回的执行器是保证不能重新配置以使用额外的线程。
@return 新创建的单线程执行器
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
b、newFixedThreadPool 方法创建的固定大小线程的源码,参数和上面的一样。
/**
创建重用固定数量线程的线程池在共享的无界队列上操作。在任何时候,最多{@code nThreads}线程将是活动的处理任务。
如果在所有线程都处于活动状态时提交额外的任务,它们将在队列中等待,直到线程可用。如果任何线程在执行期间由于失败而终止
在关闭之前,如果需要,一个新的将取代它的位置执行后续任务。池中的线程将存在直到显式地{@link ExecutorService#shutdown shutdown}。
* @param nThreads 池中的线程数
* @return 新创建的线程池
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
c、newCachedThreadPool 方法创建一个伸缩线程的源码,参数和上面的一样,第一个参数是0指的是核心线程数是0,第二个参数是Integer.MAX_VALUE指的是最大线程数是Integer的最大值(2147483647)。
/**
创建一个线程池,根据需要创建新线程,但是会重用以前构造的线程吗可用。这些池通常会提高性能执行许多短期异步任务的程序。
对{@code execute}的调用将重用先前构造的线程(如果可用)。如果没有可用的现有线程,则创建一个新的线程将被创建并添加到池中。
具有未使用60秒即被终止并移除缓存。因此,长时间闲置的池将会不消耗任何资源。注意,池与之类似属性,但细节不同(例如,超时参数)
可以使用{@link ThreadPoolExecutor}构造函数创建。
*
* @return 新创建的线程池
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4、 线程池的七大参数
int corePoolSize 核心线程池大小
int maximumPoolSize 最核心大线程池大小
long keepAliveTime 超时时间 没有人使用会自动释放
TimeUnit unit 超时单位
BlockingQueue workQueue 阻塞队列
ThreadFactory threadFactory 线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handler 拒绝策略
5、自定义线程
下面我们创建一个核心线程数是2,最大线程数是5,连接超时数10秒,队列长度是8,拒绝策略是中止策略的线程,然后处理计算。
private static void AbortPolicy() {
int corePoolSize = 2; // 核心线程数
int maximumPoolSize = 5;// 最大线程数
long keepAliveTime = 10;// 连接超时数
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(8); // 队列长度
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); //拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler);
for(int i=0; i< 15; i++) {
try {
int n = i + 1;
executor.execute(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
long num = 0;
for (long j = 1; j <= 2000000000 ; j++) {
num += j;
}
System.err.println("第" + n + "次使用名称叫" + Thread.currentThread().getName() + "线程,"+"最终入库的数是:"+num);
});
} catch (Exception e) {
e.printStackTrace();
}
}
executor.shutdown();
}
到此为止,不能再多了!