java--基础--17.5--线程--线程池

java–基础–17.5–线程–线程池


1、介绍

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

2、常用的方法

2.2、创建线程池

在这里插入图片描述

newFixedThreadPool: 创建固定线程数量的线程池
newSingleThreadExecutor: 创建单一线程的池
newCachedThreadPool: 创建线程数量自动扩容, 自动销毁的线程池
newScheduledThreadPool: 创建支持计划任务的线程池

2.2、其他方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2.3、案例

2.1.1、线程池1

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}

}

//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
ExecutorService es= Executors.newFixedThreadPool(3);
//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。 
es.submit(new MyRunnable());
es.submit(new MyRunnable());
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 
es.shutdown();

输出:
pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-2:3
pool-1-thread-2:4

2.1.2、线程池2:另外一种创建多线程的方式


//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
//返回值类型是Object 的call
public class MyCallable implements Callable {

	@Override
	public Object call() throws Exception {
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
		return null;
	}

}
//创建线程池对象
ExecutorService es=Executors.newFixedThreadPool(2);
//可以执行Runnable对象或者Callable对象代表的线程
es.submit(new MyCallable());
es.submit(new MyCallable());
//结束
es.shutdown();

输出:
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-2:0
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-2:3
pool-1-thread-2:4
pool-1-thread-1:4

2.1.3、线程池3

//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
//返回值类型是Boolean的call

public class MyCallable implements Callable<Boolean> {
	int number;

	public MyCallable(int number) {
		this.number = number;
	}

	public Boolean call() throws Exception {
		if(number%2==0){
			return true;
		}
		return false;
	}
ExecutorService es=Executors.newFixedThreadPool(2);
Future<Boolean> a=es.submit(new MyCallable(2));
Future<Boolean> b=es.submit(new MyCallable(3));
System.out.println(a.get());
System.out.println(b.get());
es.shutdown();
输出:
true
false

3、运行原理

3.1、创建线程池

public ThreadPoolExecutor(
   int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler);
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程空闲时间
  • unit:线空闲时间单位
  • workQueue:缓冲队列
  • handler:拒绝任务的处理策略

3.2、运行原理

3.2.1、当一个任务通过execute(Runnable)方法添加到线程池时

  • 如果此时线程池中的数量小于corePoolSize,创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,则新任务被添加到workQueue队列中,直到workQueue队列满,但不超过maximumPoolSize。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

3.2.2、注意事项

  • 若线程空闲时间超过keepAliveTime,非核心线程会被回收。
  • 若allowCoreThreadTimeOut为true则核心线程也会被回收。
  • 默认情况下,核心线程不会被回收。
/**
 * If false (default), core threads stay alive even when idle.
 * If true, core threads use keepAliveTime to time out waiting
 * for work.
 */
private volatile boolean allowCoreThreadTimeOut;

/**
 * Timeout in nanoseconds for idle threads waiting for work.
 * Threads use this timeout when there are more than corePoolSize
 * present or if allowCoreThreadTimeOut. Otherwise they wait
 * forever for new work.
 */

private volatile long keepAliveTime;

3.3、潜在宕机风险

使用Executors来创建要注意潜在宕机风险

3.3.1、FixedThreadPool和SingleThreadPoolPool

允许的请求队列长度为 Integer.MAX_VALUE,可能因为无限制任务队列而耗尽资源,只是出现问题的概率较小。如果新请求的到达速率超过了线程池的处理速率,那么新到来的请求将被累积起来可能会堆积大量的请求,从而导致 OOM(内存溢出)。

解决方法:
使用有界队列可以防止资源耗尽,但也因此必须要考虑拒绝策略。因为默认的AbortPolicy策略可能不是我们想要的

3.3.2、CachedThreadPool和ScheduledThreadPool

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

解决方法:
这种情况可以采用固定大小的线程池来解决这个问题。

可能有大量请求的线程池场景中,更推荐自定义ThreadPoolExecutor来创建线程池

3.4、线程池大小配置

  • 一般根据任务类型进行区分,假设CPU为N核
  • CPU密集型任务:需要减少线程数量,降低线程切换开销,可配置线程池大小为N + 1。
  • IO密集型任务:可以加大线程数量,可配置线程池大小为 N * 2。
  • 混合型任务:可以拆分为CPU密集型与IO密集型,独立配置。

3.5、案例–FixedThreadPool

3.5.1、代码

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


# 方法2 :方法1相比多了一个ThreadFactory类
  public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }



# ThreadPoolExecutor方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
             
}



 

拒绝策略:AbortPolicy,抛出运行时异常RejectedExecutionException。
这种策略丢弃任务,并抛出异常。(jdk默认策略)
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
    

3.5.2、总结

  • 线程池的初始值和最大值都设置为了指定的线程数量nThreads。
  • keepAliveTime设置成0表示多余的空闲线程会被立即终止。
  • 线程池的构造参数中还传入了一个阻塞队列,该阻塞队列的作用其实就是作为一个任务的缓冲区。

4、任务队列

BlockingQueue workQueue:用于存放Runnable对象。

4.1、直接提交(如SynchronousQueue)

  • 线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。
  • 这种策略需要线程池具有无限增长的可能性。
  • Executors.newCachedThreadPool()使用SynchronousQueue创建线程池。

4.1.1、SynchronousQueue

不保存提交的任务,数据也不会缓存到队列中,用于生产者和消费者互等对方,一起离开。

4.2、无界队列(如不具有预定义容量的LinkedBlockingQueue)

  • LinkedBlockingQueue将导致当所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)
  • 当每个任务执行互不影响时,适合于使用无界队列。
  • Executors.newFixedThreadPool(3):使用LinkedBlockingQueue创建线程池。
  • Executors.newSingleThreadExecutor():使用LinkedBlockingQueue创建线程池。

4.2.1、LinkedBlockingQueue

使用链表实现的先进先出队列
默认大小为Integer.MAX_VALUE

4.3、有界队列(如ArrayBlockingQueue)

  • 防止资源耗尽
  • 当最大线程数有限时,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷。
  • ArrayBlockingQueue:先进先出队列,创建时指定大小,有界;

4.3.1、ArrayBlockingQueue

先进先出队列
创建时指定大小
有界

4.4、优先级队列(如PriorityBlockingQueue)

PriorityBlockingQueue: 支持优先级的队列

4.5、并发队列-无界阻塞延迟队列(DelayedWorkQueue)

  • DelayedWorkQueue是ScheduledThreadPoolExecutor的静态内部类。
  • Executors.newScheduledThreadPool(3)使用DelayedWorkQueue创建线程池。

5、拒绝策略

接口RejectedExecutionHandler提供了拒绝任务处理的自定义方法。在ThreadPoolExecutor中已经包含四种拒绝策略。

  • AbortPolicy
    • 默认的拒绝策略
    • 丢弃任务,并抛出RejectedExecutionException异常
  • DiscardPolicy
    • 不能执行的任务将被丢弃。
    • 丢弃当前将要加入队列的任务本身
  • DiscardOldestPolicy
    • 在pool没有关闭的前提下,抛弃下一个将被执行的任务(工作队列头部),然后重新尝试运行该任务。
    • 如果工作队列是一个优先队列,那么该策略将抛弃优先级最高的任务,因此最好不要将DiscardOldestPolicy策略和优先级队列放在一起使用)
    • 丢弃任务队列中最旧任务
  • CallerRunsPolicy
    • 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
      • 当线程池中所有线程都被占用,并且工作队列被填满后,下一个任务会在调用executor时在主线程中执行,不进入线程池执行。
    • 此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

5.1、4种策略的测试代码

public class Worker implements Runnable {
	public void run() {
		System.out.println(Thread.currentThread().getName() + " is running");
	}

	public static void main(String[] args) {
		//1 AbortPolicy  将抛出 RejectedExecutionException.
//		RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
		
//		2 DiscardPolicy 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。运行结果也不会全部执行100个线程。
		RejectedExecutionHandler handler =  new ThreadPoolExecutor.DiscardPolicy();
		
//		3 DiscardOldestPolicy	
		//在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务(工作队列头部),然后重新尝试运行该任务
		//这样运行结果就不会有100个线程全部被执行
//		RejectedExecutionHandler handler =  new ThreadPoolExecutor.DiscardOldestPolicy();
		
//		4 CallerRunsPolicy 线程调用运行该任务的 execute 本身
//		RejectedExecutionHandler handler =  new ThreadPoolExecutor.CallerRunsPolicy();
		subTask(handler);
	}
	public static void	subTask(RejectedExecutionHandler handler){
		int corePoolSize = 5;
		int maxPoolSize = 10;
		long keepAliveTime = 5;
		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10);
		ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
				queue, handler);
		for (int i = 0; i < 60; i++) {
			executor.execute(new Worker());
		}
		executor.shutdown();
	}
}

5.2、AbortPolicy

  • 默认的拒绝策略
  • 丢弃任务,并抛出RejectedExecutionException异常
pool-1-thread-10 is running
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Test.Worker@761f568f rejected from java.util.concurrent.ThreadPoolExecutor@2830803a[Running, pool size = 10, active threads = 9, queued tasks = 0, completed tasks = 28]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
	at Test.Worker.subTask(Worker.java:38)
	at Test.Worker.main(Worker.java:28)

5.3、DiscardPolicy

  • 不能执行的任务将被丢弃。
  • 丢弃当前将要加入队列的任务本身

5.4、DiscardOldestPolicy

  • 在pool没有关闭的前提下,抛弃下一个将被执行的任务(工作队列头部),然后重新尝试运行该任务。
  • 如果工作队列是一个优先队列,那么该策略将抛弃优先级最高的任务,因此最好不要将DiscardOldestPolicy策略和优先级队列放在一起使用)
  • 丢弃任务队列中最旧任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

5.5、CallerRunsPolicy

  • 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
    • 当线程池中所有线程都被占用,并且工作队列被填满后,下一个任务会在调用executor时在主线程中执行,不进入线程池执行。
  • 此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

输出:

pool-1-thread-5 is running
pool-1-thread-8 is running
pool-1-thread-6 is running
main is running
main is running
main is running
main is running

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值