线程池的自我介绍
思考
如果不使用线程池,我们可能需要每次任务都新开一个线程来处理。
- 如果只有一个线程
public class oneThread {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.start();
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println("正在执行任务");
}
}
}
//正在执行任务
- 如果有十个线程
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Task());
thread.start();
}
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println("正在执行任务");
}
}
/*
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
正在执行任务
*/
- 如果任务数量上升到1000个怎么办?
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(new Task());
thread.start();
}
这样的开销太大,会产生反复创建并销毁线程所带来的开销问题。在大量创建线程的时候内存消耗会很大,在线程运行完毕之后需要回收,又会给垃圾回收器带来压力。操作系统创建的线程是有上限的,一直不停创建线程,可能会超过上限导致出错,报出OOM异常。
为什么要使用线程池
- 返回创建线程开销大
- 过多的线程会占用太多的内存
创建和停止线程池
线程池构造函数的参数
corePoolSize
线程池在完成初始化后,默认情况下,线程池中并没有任务线程,线程池会等待任务带来时,在创建新线程去执行任务
maxPoolSize
线程池有可能会在核心线程池的基础上,额外创建一些线程,但是这些线程数会有一个上限,这就是最大量maxPoolSize
添加线程的规则
- 如果线程数小于corePoolSize.即使其他工作的线程处于空闲状态,也会创建一个新线程来运行新任务
- 如果线程数等于corePoolSize但少于maxPoolSize,则将任务放入队列
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
- 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务
keepAliveTime
如果线程池当前的线程多于corePoolSize,那么如果多于的线程空闲时间超过keepAliveTime,他们就会被终止
threadFactory
新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有相同的优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等
workQueue
有3种常见的队列类型
- 直接交换
SynchronousQueue
任务不会太多,只是通过队列对任务进行简单的中转,然后交到线程池去执行。
- 无界队列
LinkedBlockingQueue
最大值为Integer.MAX_VALUE,如果任务的处理速度小于任务提交的速度,会造成队列中任务的堆积,甚至OOM异常
- 有界队列
ArrayBlockingQueue
需要设置任务的数量
线程需要手动创建还是自动创建
手动创建更好,因为这样可以让我们更加明确线程的运行规律,避免资源耗尽的风险
自动创建可能带来的问题
newFixedThreadPool
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
service.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
由于传进去的LinkedBlockingQueue是没有容量上限的,所有当请求数越来越多,并且无法及时处理完毕的时候,也就是请求推挤的时候,会容易造成占用大量的内存,可能会导致OOM
OOM异常演示
//-Xmx10m -Xms10m
private static ExecutorService service = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
service.execute(new Task());
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500000000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
newSingleThreadExecutor
跟newFixedThreadPool 线程池一样,任务堆积时可能出现OOM异常
private static ExecutorService service = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
service.execute(new Task());
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//pool-1-thread-1
//pool-1-thread-1
//pool-1-thread-1
//... 只有一个线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool
可缓存线程池
特点:无界的线程池,具有自动回收多余线程的功能
private static ExecutorService service = Executors.newCachedThreadPool();
public static void main(String[] args) {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
service.execute(new Task());
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//注意控制台打印
//pool-1-thread-423
//...线程数在一直增加
//pool-1-thread-3585
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这里的弊端在与第二个参数maxPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM异常
newSingleThreadExecutor
private static ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
public static void main(String[] args) {
//几秒后打印出内容
service.schedule(new Task(),3, TimeUnit.SECONDS);
//周期性执行任务 第一次是线程启动后3秒执行 以后每隔一秒执行任务
service.scheduleAtFixedRate(new Task(),3,1,TimeUnit.SECONDS);
}
class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("正在执行任务");
}
}
正确创建线程的方法
- 根据不同的任务场景,自己设置线程池的参数,比如我们的内存有多大,我们想给线程去什么名字,任务拒绝后应该怎么记录日志。
线程池里的线程数量设置为多少比较合适
-
CPU密集型(加密等)
最佳的线程为CPU核心数的1-2倍
-
IO型(读写数据库,文件,网络读写)
最佳线程数一般会大于cpu数量核心数的很多倍。
-
通用公式
线程数 = CPU核心数 * (1+平均等待时间/平均工作时间)
停止线程池的正确方法
shutdown
初始化关闭线程操作,需要等待线程正在执行的任务以及队列中已经存在的任务都执行完毕后,才会关闭线程池
private static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
service.execute(new Task());
}
Thread.sleep(1500); //主线程睡眠1500毫秒
service.shutdown();//shutdown()并不会立马停止线程池
service.execute(new Task());
//在执行shutdown() 执行之后,继续添加任务到线程池。
//会报RejectedExecutionException异常
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//运行代码
isShutdown
判断线程池是否处于shutdown()状态。并不代表线程池已经停止
private static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
service.execute(new Task());
}
Thread.sleep(1500);
System.out.println(service.isShutdown()); //false
service.shutdown();
System.out.println(service.isShutdown()); //true
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
isTerminated
线程池真正停止工作
private static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
service.execute(new Task());
}
Thread.sleep(100);
System.out.println(service.isShutdown()); //false
service.shutdown();
System.out.println(service.isShutdown()); //true
System.out.println("----------");
System.out.println(service.isTerminated()); //false
Thread.sleep(10000);
System.out.println(service.isTerminated()); //true
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
awaitTermination
等待一定时间,判断线程时候结束。如果结束返回true, 没有则返回false
private static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
service.execute(new Task());
}
System.out.println(service.awaitTermination(1,TimeUnit.SECONDS)); //false
System.out.println(service.awaitTermination(10,TimeUnit.SECONDS)); //true
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
shutdownNow
会中断正在运行的线程,并且返回队列中的线程
private static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
service.execute(new Task());
}
Thread.sleep(500);
List<Runnable> list = service.shutdownNow();
System.out.println(list);
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"中断了");
}
}
}
任务的拒绝
拒绝的时机
-
当Executor关闭时,提交新任务会被拒绝
-
当Exector对最大线程和工作队列容量使用有限边界并且饱和时
4中拒绝的策略
-
AbortPlicy
直接拒绝,抛出异常
-
DiscardPolicy
直接丢弃
-
DiscardOldestPolicy
丢弃最老的任务
-
CallerRunsPolicy
提交任务的线程来执行
优点:利用提交的线程的执行任务,会降低线程池接收任务的速度,让线程池有时间来执行其他任务。
线程池的实现原理,源码分析
线程的组成部分
-
线程池管理器
来管理线程的创建,关闭操作
-
工作线程
创建出来执行任务的线程
-
任务队列
存放任务的队列
-
任务
要执行的任务
线程池,ThreadPoolExecutor,ExecutorService,Executors,Executor的关系
Executor
顶层的接口,只有一个方法
public interface Executor {
void execute(Runnable command);
}
ExecutorService
继承自executor, 扩展了一些管理线程池的方法
Executors
工具类,方便创建线程池。
ThreadPoolExecutor
线程池
线程池的状态
running
接收新任务并处理排队任务
shutdown
不接受新任务,但处理排队任务
stop
不接收新任务,不处理排队的任务,中断正在执行的任务。
tidying
所有的任务都已终止,workcount为0,线程切换到tidying状态,并将运行terminate()
terminate
terminate() 运行完成
线程池的注意点
- 避免任务的堆积
- 避免线程数的过度增加
- 排查线程泄露