你了解4种常用的线程池吗?

Hello朋友们,我是RatelBlog的李某人。

今天我们来聊一聊线程池,什么是线程池?

线程池其实就是一堆线程的集合,我们把这个集合称为线程池。在并发编程中使用线程池可以很好的提高服务
的性能,可以用来管理和维护线程以及复用空闲的线程,从而避免频繁的创建和销毁线程所消耗的系统资源。
先来了解一下线程池的5种状态

1.Running(运行状态):线程池可以接受新的任务,也可以处理阻塞队列中的任务。执行shutdown()方法可进入
待关闭状态(ShutDown),执行shutdownNow()方法可以进入停止状态(Stop)。

2.ShutDown(待关闭状态):线程池不再接受新的任务,继续处理阻塞队列中的任务。当队列中的任务为空时,
并且线程池中执行的任务为空会进入整理状态(Tidying)。

3.Stop(停止状态):线程池不再接受新的任务,也不处理阻塞队列中的任务。并会中止正在处理的任务,
当执行的任务为空会进入整理状态(Tidying)。

4.Tidying(整理状态):所有的任务都已结束,线程池会变成整理状态(Tidying)。并会调用终止方法
terminated()。terminated()方法在ThreadPoolExecutor类中是空的,没有任何实现需要自己重写。
终止方法执行完毕后会进入Terminated(终止状态)。

5.Terminated(终止状态):线程池完全终止,并完成了所有资源的释放。
在Java中可以通过Executors类来创建线程池,内部还是使用的ThreadPoolExecutor实现的。

根据阿里巴巴的开发者手册,不建议直接使用Executors来直接创建线程池,因为在创建
(缓存线程池)CachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而
导致OOM异常。在创建定长线程池(FixedThreadPool)和单线程化线程池(SingleThreadPool)时,允许
的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

所以在实际的工作中,还是建议直接通过ThreadPoolExecutor来创建线程池。

那么我们先来了解一下ThreadPoolExecutor参数说明。

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory,
						  RejectedExecutionHandler handler) {
	if (corePoolSize < 0 ||
		maximumPoolSize <= 0 ||
		maximumPoolSize < corePoolSize ||
		keepAliveTime < 0)
		throw new IllegalArgumentException();
	if (workQueue == null || threadFactory == null || handler == null)
		throw new NullPointerException();
	this.corePoolSize = corePoolSize;
	this.maximumPoolSize = maximumPoolSize;
	this.workQueue = workQueue;
	this.keepAliveTime = unit.toNanos(keepAliveTime);
	this.threadFactory = threadFactory;
	this.handler = handler;
}

注意:
核心线程:就是闲置不会被销毁的线程,可以设置allowCoreThreadTimeout=true(默认false),超过闲置
时间会被销毁。

非核心线程:超过闲置时间,线程会被销毁。
参数作用
int corePoolSize

核心线程数。当线程数小于核心线程数时,即使有空闲线程,线程池也会优先创建新线程处理。

注意:

线程池新建线程的时候,当前线程总数< corePoolSize,新建的线程即为核心线程。
线程池新建线程的时候,当前线程总数> corePoolSize, 且阻塞队列已满,这时新建一个线程来
执行新提交的任务即为非核心线程。

int maximumPoolSize

最大线程数。表示线程池中最多能够创建的线程数量。

注意:

线程总数 = 核心线程数 + 非核心线程数

long keepAliveTime

线程空闲时间。keepAliveTime即为空闲线程允许的最大的存活时间。如果一个非核心线程空闲状态的时长超过keepAliveTime了,就会被销毁掉。

注意:如果设置allowCoreThreadTimeOut = true,核心线程超时也会销毁。

TimeUnit unit

空闲线程时间单位。

注意:

NANOSECONDS:1微毫秒 = 1微秒 / 1000

MICROSECONDS:1微秒 = 1毫秒 / 1000

MILLISECONDS:1毫秒 = 1秒 /1000

SECONDS:秒

MINUTES:分

HOURS:小时

DAYS:天

BlockingQueue<Runnable> workQueue

保存任务的阻塞队列。

注意:

当核心线程都在工作的时候,新提交的任务就会被添加到这个阻塞队列中进行排队等待;如果阻塞队列也满了,线程池就新建非核心线程去执行任务。workQueue维护的是等待执行的Runnable对象。

ThreadFactory threadFactory
创建线程的工厂。
RejectedExecutionHandler handler

任务拒绝处理器。

两种情况会拒绝处理任务:

1.当线程数已经达到maximumPoolSize,切队列已满,会拒绝新任务。

2.当线程池处于ShutDown|Stop拒绝接受新任务。

ThreadPoolExecutor默认有四个拒绝策略:

1、ThreadPoolExecutor.AbortPolicy()   直接抛出异常RejectedExecutionException
2、ThreadPoolExecutor.CallerRunsPolicy()    直接调用run方法并且阻塞执行
3、ThreadPoolExecutor.DiscardPolicy()   忽视,什么都不会发生
4、ThreadPoolExecutor.DiscardOldestPolicy()  丢弃在队列中队首的任务     

当然也可以实现RejectedExecutionHandler接口,可自定义处理器 。

线程池工作流程

ThreadPoolExecutor参数大概了解了,然后咱们了解下线程池的工作流程。

1.提交任务
2.核心线程数未达到corePoolSize,创建核心线程执行任务。
3.核心线程数达到corePoolSize,任务队列未满进入队列等待。
4.队列已满,总线程池未满创建非核心线程执行任务。
5.总线程已满,按照拒绝策略执行。

常用的 workQueue 类型

缓存线程池--CachedThreadPool

newCachedThreadPool将创建一个可缓存的线程池。

public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
								  60L, TimeUnit.SECONDS,
								  new SynchronousQueue<Runnable>());
}

阿里巴巴开发者手册:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程从而导致OOM.
Integer.MAX_VALUE


特点:
1.核心线程数为0,总线程大小为Integer.MAX_VALUE 2147483647,所以也可以看作是线程数无限制。
2.有空闲线程就复用,否则就新建线程处理任务。
3.默认的空闲时间为60秒。


OK 我们来模拟一个场景,首先拿到了一个批次的用户集合,给这些用户分发消息,那么就可以使用到线程
池并发去处理任务。

public static void main(String[] args) {
	String[] users = {"张三","李四","王五","司总"};
	ExecutorService executorService = Executors.newCachedThreadPool();
	for (String user : users) {
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000L);
				} catch (Exception e) {
					e.printStackTrace();
				}
				logger.info(Thread.currentThread().getName()+"执行分发消息任务:"+user);
			}
		});
	}
}

pool-1-thread-4执行分发消息任务:司总
pool-1-thread-2执行分发消息任务:李四
pool-1-thread-1执行分发消息任务:张三
pool-1-thread-3执行分发消息任务:王五

注意:
如果上面这个案例不使用线程池就单线程去执行,就需要耗时4秒。所以使用线程池可以大大的提高效率。

定长线程池--FixedThreadPool

newFixedThreadPool创建一个固定长度的线程池,每次提交一个任务的时候就会创建一个新的线程,直到
达到线程池的最大数量限制。

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads,
								  0L, TimeUnit.MILLISECONDS,
								  new LinkedBlockingQueue<Runnable>());
}

阿里巴巴开发者手册:允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致OOM.
public LinkedBlockingQueue() {
	this(Integer.MAX_VALUE);
}


特点:
1.线程全部都是核心线程,需要传入初始线程大小数。
2.超出的线程会在队列中等待。



public static void main(String[] args) {
	ExecutorService executorService = Executors.newFixedThreadPool(10);
	for (int i = 0; i < 11; i++) {
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				logger.info(Thread.currentThread().getName() + "~~~");
			}
		});
	}
}

单线程线程池--SingleThreadExecutor

newSingleThreadExecutor创建一个单线程线程池。

public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1, 1,
								0L, TimeUnit.MILLISECONDS,
								new LinkedBlockingQueue<Runnable>()));
}

阿里巴巴开发者手册:允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致OOM.
public LinkedBlockingQueue() {
	this(Integer.MAX_VALUE);
}


特点:
1.只有一个核心线程处理任务。
2.所有任务按照工作队列的排队顺序执行,先进先出的顺序。
3.单个线程的线程池就是线程池中只有一个线程负责任务,所以 corePoolSize 和 maximumPoolSize 的数值
都是为1;当这个线程出现任何异常后,线程池会自动创建一个线程,始终保持线程池中有且只有一个存活的
线程。


public static void main(String[] args) {
	ExecutorService executorService = Executors.newSingleThreadExecutor();
	for (int i = 0; i < 4; i++) {
		executorService.execute(new Runnable() {
			@Override
			public void run() {
				logger.info(Thread.currentThread().getName() + "~~~");
			}
		});
	}
}

pool-1-thread-1~~~
pool-1-thread-1~~~
pool-1-thread-1~~~
pool-1-thread-1~~~

定时线程池--ScheduledThreadPool()​​​​​​​

newScheduledThreadPool创建一个固定长度的线程池,并且以延迟或者定时的方式去执行任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
	return new ScheduledThreadPoolExecutor(corePoolSize);
}


public ScheduledThreadPoolExecutor(int corePoolSize) {
	super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
		  new DelayedWorkQueue());
}

注意:每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。


1.scheduleAtFixedRate:表示以固定频率执行的任务,如果当前任务耗时较多,超过定时周期period,
则当前任务结束后会立即执行。

第一个command参数是任务实例,
第二个initialDelay参数是初始化延迟时间,
第三个period参数是间隔时间,
第四个unit参数是时间单元。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
											  long initialDelay,
											  long period,
											  TimeUnit unit);

public static void main(String[] args) {
	ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
	executorService.scheduleAtFixedRate(new Runnable() {
		@Override
		public void run() {
			logger.info(Thread.currentThread().getName()+"定时任务:每秒执行一次");
		}
	},0,1, TimeUnit.SECONDS);
}

pool-1-thread-1定时任务:每秒执行一次
pool-1-thread-1定时任务:每秒执行一次
pool-1-thread-2定时任务:每秒执行一次
pool-1-thread-1定时任务:每秒执行一次




2.scheduleWithFixedDelay:表示以固定延时执行任务,延时是相对当前任务结束为起点计算开始时间。

第一个command参数是任务实例,
第二个initialDelay参数是初始换延迟时间,
第三个delay参数是延迟间隔时间,
第四个unit参数是时间单元

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
												 long initialDelay,
												 long delay,
												 TimeUnit unit);


public static void main(String[] args) {
	ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
	executorService.scheduleWithFixedDelay(new Runnable() {
		@Override
		public void run() {
			logger.info(Thread.currentThread().getName()+"延迟任务:上一次任务结束后,延迟2秒后执行");
		}
	},0,2, TimeUnit.SECONDS);
}

pool-1-thread-1延迟任务:上一次任务结束后,延迟2秒后执行
pool-1-thread-1延迟任务:上一次任务结束后,延迟2秒后执行
pool-1-thread-2延迟任务:上一次任务结束后,延迟2秒后执行

execute与submit区别

1.定义的位置不同

execute定义在Executor接口中,ExecutorService接口继承了Executor接口,AbstractExecutorService类
实现了ExecutorService接口,ThreadPoolExecutor继承了AbstractExecutorService抽象类。

public interface Executor {
  void execute(Runnable command);
}

public interface ExecutorService extends Executor 

public abstract class AbstractExecutorService implements ExecutorService

public class ThreadPoolExecutor extends AbstractExecutorService


submit是定义在ExecutorService接口中,AbstractExecutorService类实现了ExecutorService接口

public abstract class AbstractExecutorService implements ExecutorService

public class ThreadPoolExecutor extends AbstractExecutorService

2.入参不同

只能接受Runnable
void execute(Runnable command);


submit方法被重载了三次,分别对用三个不同的参数

public Future<?> submit(Runnable task)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回null。

public <T> Future<T> submit(Runnable task, T result)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回
给定的结果。

public <T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。 

3.返回值
execute没有返回值

submit返回future,可以获取到线程返回的结果数据还可以返回异常。

public Future<?> submit(Runnable task)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回null。


public static void main(String[] args) throws Exception {
	ExecutorService executorService = Executors.newCachedThreadPool();
	Future<?> future = executorService.submit(new Runnable() {
		@Override
		public void run() {
			logger.info("线程池执行");
			try {
				Thread.sleep(5000L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	});
	//future.get()会阻塞主线程
	logger.info((String) future.get());
	logger.info(Thread.currentThread().getName()+"主~~~~");
}

[pool-1-thread-1] INFO com.testing.rest_template_demo.ThreadPoolTest - 线程池执行
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - null
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~

其它方法:
//尝试取消此任务的执行 返回true取消成功 false取消失败
future.cancel(true)
//正常终止、例外或取消——在所有这些情况下,此方法都将返回true
future.isDone();
//如果此任务在完成前被取消,则返回true
future.isCancelled();
public <T> Future<T> submit(Runnable task, T result)
提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功时将会返回
给定的结果。

public static void main(String[] args) throws Exception {
	ExecutorService executorService = Executors.newCachedThreadPool();
	Future<String> future = executorService.submit(new Runnable() {
		@Override
		public void run() {}
	}, "结果");
	logger.info((String) future.get());
	logger.info(Thread.currentThread().getName()+"主~~~~");
}

[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 结果
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~





public static void main(String[] args) throws Exception {
	ExecutorService executorService = Executors.newCachedThreadPool();
	Future<String> future = executorService.submit(new Runnable() {
		@Override
		public void run() {
			int i = 1 / 0;
		}
	}, "结果");

	try {
		logger.info((String) future.get());
	} catch (Exception e) {
		logger.info("捕获到异常" + e.getMessage());
	}

	logger.info(Thread.currentThread().getName() + "主~~~~");
}


[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 捕获到异常java.lang.ArithmeticException: / by zero
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~
public <T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。 


public static void main(String[] args) throws Exception {
	ExecutorService executorService = Executors.newCachedThreadPool();
	Future future = executorService.submit(new MyCallable());
	logger.info(future.get().toString());
	logger.info(Thread.currentThread().getName() + "主~~~~");
}

static class MyCallable implements Callable {
	@Override
	public Object call() throws Exception {
		return "我是Callable返回数据";
	}
}


[main] INFO com.testing.rest_template_demo.ThreadPoolTest - 我是Callable返回数据
[main] INFO com.testing.rest_template_demo.ThreadPoolTest - main主~~~~

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值