多线程-线程池-

   创建线程,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存,因为正常来说线程执行完毕后死亡,线程对象变成垃圾!那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务哪?我们使用线程池就能很好地解决这个问题。
 

线程池的作用:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

创建线程池方式

1.自己创建线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory,handler);

1.corePoolSize:线程池中的常驻核心线程数
2.maxinumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于一
3.keepAliveTime:多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
4.unit:keepAliveTime的单位
5.workQueue:任务队列,被提交但是尚未被执行的任务。
6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
7.handler:拒绝策略,表示当队列满了并且工作线程-大于等于线程池的数量最大线程数(maxinumPoolSize)时如何来拒绝请求执行的runnable的策略。
线程池的执行流程
提交任务时,线程池中的线程数小于核心线程数时,会去创建线程执行任务,当提交任务线程池中的线程数达到 核心线程数的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。
注意:当阻塞队列已满依然有线程进来,那么会开启新的线程来执行新进来的任务,而不会优先执行阻塞队列中的任务。

5.workQueue:工作队列
存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。

JDK默认的工作队列有五种:

ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
SynchronousQueue(一个不缓存任务的阻塞队列)
生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。。

PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现

DelayQueue(这是一个无界阻塞延迟队列)
底层基于 PriorityBlockingQueue 实现的,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,而队列头部的元素是过期最快的元素。

7.拒绝策略

AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。

CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大

 DiscardPolicy: 直接丢弃,其他啥都没有 不抛出异常

DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入通常而言,这四种拒绝策略我们一般都不太适用我们的业务场景,我们一般都会自定义自己的拒绝策略,将线程任务放进kafaka或者mq消息队列中

Executors四种线程池
1.Executors.newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。简单说1池1线程

2.Executors.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。简单说1池N线程(N自己决定)

3.Executors.newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。简单说1池动态扩容线程

4.Executors.newScheduledThreadPool
创建一个固定大小的线程池。此线程池支持定时以及周期性执行任务的需求

		ExecutorService singPoolExecutor=Executors.newSingleThreadExecutor();//创建一个单线程的线程池
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);//创建固定大小的线程池
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();//创建可缓存的线程池
		 ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);//创建固定大小 线程数 支持定时和周期性任务

向线程池提交任务execute()方法、submit方法

execute()方法和submit()的区别

1)execute只能提交Runnable类型的任务,submit既能提交Runnable类型任务也能提交Callable类型任务

2)execute()没有返回值,submit有返回值,submit方法可以用于提交需要有返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判读是否执行成功,并且还可以通过get()方法来获取返回值。

3)异常的处理,execute会直接抛出任务执行时的异常,可以用try、catch来捕获,submit方法,异常会被吞掉,可通过Future的get方法将任务执行时的异常重新抛出。

public class Test {
	
	public static void main(String[] args) {
				
		ExecutorService singPoolExecutor=Executors.newSingleThreadExecutor();//创建一个单线
		Test test=new Test();
		singPoolExecutor.execute(()->test.say("execute方法"));
		singPoolExecutor.submit(()->test.say("submit方法"));
				
	}	
	public void say(String say) {
		System.out.println(say);
		throw new RuntimeException("抛出了异常:"+say);		
	}
}

结果:

execute方法
Exception in thread "pool-1-thread-1" submit方法
java.lang.RuntimeException: 抛出了异常:execute方法
    at com.example.demo.test.Test.say(Test.java:23)
    at com.example.demo.test.Test.lambda$0(Test.java:16)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

 结果可见,只有execute方法直接抛出了异常,submit方法方法没有打印异常信息

测试使用Future的get获取结果

public class Test {
	
	public static void main(String[] args) {
				
		ExecutorService singPoolExecutor=Executors.newSingleThreadExecutor();//创建一个单线
		Test test=new Test();
		singPoolExecutor.execute(()->test.say("execute方法"));
		Future<?> future = singPoolExecutor.submit(()->test.say("submit方法"));
		try {
			Object x = future.get();
			System.out.println("submit方法的get方法");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		singPoolExecutor.shutdown();
				
	}	
	public void say(String say) {
		System.out.println(say);
		throw new RuntimeException("抛出了异常:"+say);		
	}
}

结果:

结果可见,使用submit时在使用future.get()方法才抛出异常,否则异常会被吞没

线程池大小设置:

而线程池的状态有以下 5 种:

  1. RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态。
  2. SHUTDOWN:关闭状态,不再接受新任务提交,但是会将已保存在任务队列中的任务处理完。
  3. STOP:停止状态,不再接受新任务提交,并且会中断当前正在执行的任务、放弃任务队列中已有的任务。
  4. TIDYING:整理状态,所有的任务都执行完毕后(也包括任务队列中的任务执行完),当前线程池中的活动线程数降为 0 时的状态。到此状态之后,会调用线程池的 terminated() 方法。
  5. TERMINATED:销毁状态,当执行完线程池的 terminated() 方法之后就会变为此状态。

线程池状态转移

线程池的状态转移有两条路径:

  • 当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。
  • 当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。

线程状态转换的流程如下图所示: 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当 http-nio-7001-exec- 线程池的数量突然增高时,可能是由以下原因导致的: 1. 突发流量:如果有大量用户同时访问网站或请求需要处理大量数据,可能会导致线程池数量突然增加。 2. 长时间运行的请求:如果请求需要很长时间才能处理完毕,线程池可能会一直保持高水平。这可能是由于处理请求的代码有性能问题。 3. 竞态条件:当不同的线程需要共享数据时,可能会出现竞争条件。这可能会导致某些线程等待其他线程完成工作,从而导致线程池数量增加。 4. 内存泄漏:如果应用程序中存在内存泄漏,可能会导致线程池数量增加。这是因为线程需要占用内存,而内存泄漏则会导致内存无法释放,从而导致线程不断增加。 要解决此问题,可以通过以下方式: 1. 检查代码逻辑,查找潜在的性能问题,例如避免使用锁、减少数据访问等。 2. 应该检查应用程序中的垃圾回收机制和内存分配,以确保内存使用状况正常。 3. 可以增加线程池的大小,以便处理更多的请求。虽然这并不能解决问题的根源,但如果您确定出现了时间敏感的性能问题,则可以使用此方法暂时缓解问题。 4. 内存泄漏是一个严重的问题,应该彻底检查应用程序和底层框架,以确保它们正常运行。 总之,应该确保线程池不会过度使用资源,且需要密切监视线程池并进行必要的调整以支持流量变化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值