线程池 ThreadPool
1、New Thread 弊端
- 每次 new Thread 新建对象,性能差。
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
- 缺少更多功能,如更多执行、定期执行、线程中断。
2、线程池的好处
-
重用存在的线程,减少对象创建、消亡的开销,性能佳。
-
可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
-
提供定时执行、定期执行、单线程、并发数控制等功能。
3、线程池 - ThreadPoolExecutor

3.1 核心参数
corePoolSize
:核心线程数量。maximumPoolSize
: 线程最大线程数。workQueue
: 阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。keepAliveTime
: 线程没有任务执行时,最多保持多久时间终止。unit
: keepAliveTime 的时间单位。threadFactory
: 线程工厂,用来创建线程。rejectHandler
: 当拒绝处理任务时的策略。
3.2 核心方法
execute()
: 提交任务,交给线程池执行。submit()
: 提交任务,能够返回执行结果 execute+Future.shutdown()
: 关闭线程池,等待任务都执行完。shutdownNow()
: 关闭线程池,不等待任务执行完。
3.3 监控方法
- getTaskCount(): 线程池已执行和未执行的任务总数。
- getCompletedTaskCount(): 已完成的任务数量。
- getPoolSize(): 线程池当前的线程数量。
- getActiveCount(): 当前线程池中正在执行任务的线程数量。
package com.icao;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;
/**
* @author
* @title: NewCachedThreadPoolExample
* @description: TODO
* @date 2020/4/16 9:30
*/
@Slf4j
public class NewCachedThreadPoolExample {
public static Integer clientCount = 5000;
public static Integer threadCount = 200;
public static List<Integer> list = new Vector<>();
public static void main(String[] args) throws Exception {
// 声明 newCachedThreadPool 线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
// Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
final Semaphore semaphore = new Semaphore(threadCount);
final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
for ( int i = 0; i<clientCount; i++) {
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
add();
/** 线程监控 log - start**/
// 当前线程池中正在执行任务的线程数量
int activeCount =
((ThreadPoolExecutor)newCachedThreadPool).getActiveCount();
log.info("ActiveCount={}",activeCount);
// 线程池已执行和未执行的任务总数
long taskCount =
((ThreadPoolExecutor)newCachedThreadPool).getTaskCount();
log.info("taskCount={}",taskCount);
/** 线程监控 log - end**/
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
log.error("exception", e);
}
// //将计数值减1
countDownLatch.countDown();
}
});
}
countDownLatch.await();
newCachedThreadPool.shutdown();
log.info("list.size() = {}",list.size());
// 线程池当前的线程数量
int poolSize =
((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
log.info("poolSize={}",poolSize);
// 已完成的任务数量。
long completedTaskCount =
((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
log.info("completedTaskCount={}",completedTaskCount);
}
private static void add() {
list.add(0) ;
}
}
3.4 Executors 提供四种线程池
- Executors.newCachedThreadPool
- Executors.newFixedThreadPool
- Executors.newScheduledThreadPool
- Executors.newSingleThreadExecutor
3.4.1 newCachedThreadPool
Executors.newCachedThreadPool :
创建一个可缓存线程池
,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务
。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newCachedThreadPool 线程池特点是:
1、工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),?这样可灵活的往线程池中添加线程。
2、如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3、在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
代码模拟高并发:
package com.icao;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;
/**
* @author
* @title: NewCachedThreadPoolExample
* @description: TODO
* @date 2020/4/16 9:30
*/
@Slf4j
public class NewCachedThreadPoolExample {
public static Integer clientCount = 5000;
public static Integer threadCount = 200;
public static List<Integer> list = new Vector<>();
public static void main(String[] args) throws Exception {
long startMili=System.currentTimeMillis();// 当前时间对应的毫秒数
log.info("/**开始 "+startMili);
// 声明 newCachedThreadPool 线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
// Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
final Semaphore semaphore = new Semaphore(threadCount);
// countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
// 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
for ( int i = 0; i<clientCount; i++) {
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
// 方法 acquire( int permits ) 参数作用,及动态添加 permits 许可数量
// acquire( int permits ) 中的参数可以这么理解, new Semaphore(6) 表示初始化了 6个通路, semaphore.acquire(2) 表示每次线程进入将会占用2个通路,semaphore.release(2) 运行时表示归还2个通路。没有通路,则线程就无法进入代码块。
// 而semaphore.acquire() + semaphore.release() 在运行的时候,其实和 semaphore.acquire(1) + semaphore.release(1) 效果是一样的。
semaphore.acquire();
add();
log.info("CurrentThreadId()={},list.size()={}",
Thread.currentThread().getId(),list.size());
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
log.error("exception", e);
}
// //将计数值减1
countDownLatch.countDown();
}
});
}
// 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
countDownLatch.await();
newCachedThreadPool.shutdown();
// 这里使用countDownLatch 是为了保证最后执行 log.info("list.size() = {}",list.size());
log.info("list.size() = {}",list.size());
//这里加入需要测试的代码
long endMili=System.currentTimeMillis();//结束时间
log.info("/**结束 s"+endMili);
log.info("/**总耗时为:"+(endMili-startMili)+"毫秒");
// 线程池当前的线程数量
int poolSize =
((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
log.info("poolSize={}",poolSize);
// 已完成的任务数量。
long completedTaskCount =
((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
log.info("completedTaskCount={}",completedTaskCount);
}
private static void add() {
list.add(0) ;
}
}
3.4.2 newFixedThreadPool
Executors.newFixedThreadPool :
创建固定大小(定长)的线程池
,可控制线程最大并发数。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,超出的线程会在队列中等待
。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
。
3.4.3 newScheduledThreadPool
Executors.newScheduledThreadPool :
创建一个定长线程池,支持定时及周期性任务执行。
3.4.4 newSingleThreadPool
Executors.newSingleThreadExecutor :
创建一个单线程化的线程池
,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
。