本文介绍多线程在使用时由直接new Thread()到Executors.newCachedThreadPool(),再到直接new ThreadPoolExecutor()的过程。
背景:
项目需要在Servlet前加个Filter做数据的转发,只是数据的简单透传,为了不影响用户的体验,而采用多线程来实现。
最简单的实现
直接在filter中new Thread来实现,如下:
但是访问量一大,这种方法会把系统搞死。
线程池实现
后面采用线程池的方式实现,虽然之前也有研究过线程池,但是对线程池的记忆最深的就是线程池是对线程的缓存,重复利用,避免线程的重复创建和销毁带来的性能损耗,还有就是ExecutorService类提供的几种线程池方式:signle线程数的线程、线程数固定的线程、线程数不固定的线程。当时也没多考虑,担心影响系统的并发数,就直接使用Executors.newCachedThreadPool()缓存的线程池,如下:
但是上线一段时间后,运维反馈系统不太稳定,但也没有直接原因表示是该线程池导致,接着研究。
自己的设想
后面觉得这里就是数据的转发,其实也就是一个典型的生产者和消费者模型,可以使用一个阻塞队列来把请求数据缓冲起来,再使用一个线程池把数据消费掉,想到之前研究过的并发大师Doug Lea写的几个并发队列,不错,再比较下这几个队列使用的是显示锁还是CAS就能确定使用哪个队列了,有搞头...
线程池的原理
古人云:没文化,真可怕。正当我开开心心的写我的生产者消费者,使用线程池来消费队列里的任务时悲剧的发现,线程池也有队列来缓存任务,我上面的设计就是线程池的另一个作用,线程池不只是对线程的管理,避免线程的重复创建和销毁带来的性能损耗,而且还对任务的缓冲,提高系统的响应速度。
比较靠谱的实现
通过直接研究线程池的实现类ThreadPoolExecutor,而不是之前只关注线程池的铺助类和接口,使用线程数量固定、LinkedBlockingQueue为缓存任务的队列,如果任务队列满了就做丢弃处理,如下:
ThreadPoolExecutor类的构造函数为:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
创建一个线程池需要输入几个参数:
- corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
- maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
- TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
- workQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
- threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
- RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
- 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
相关推荐
这里线程池线程大小还需要判断一次;前面的判断过程中并没有加锁,因此可能在execute方法判断的时候poolSize小于corePoolSize,而判断完之后,在其他线程中又向线程池提交了任务,就可能导致poolSize不小于...
线程池是一种线程使用模式,它预先创建了一组可重用的线程,当有任务需要执行时,不再直接创建新的线程,而是从线程池中取出一个空闲线程来执行任务,完成任务后线程返回到线程池而不是直接销毁。这样可以避免频繁地...
我们可以通过@Scheduled注解来定义一个方法为定时任务,例如每5秒执行一次,或者在特定时间点执行。 当涉及线程池和定时任务时,异常处理是一个不可忽视的话题。线程池中的任务可能会抛出异常,如果不进行妥善处理...
通过定义JobDetail和Trigger,我们可以设定任务的执行时间,例如每小时执行一次或者每天特定时间执行。Spring的TaskScheduler或SchedulerFactoryBean也可以用来调度任务,但Quartz提供了更高级和灵活的调度功能。 ...
- **关闭线程池**:线程池不是一次性资源,使用完毕后需要调用`shutdown()`或`shutdownNow()`方法进行关闭,防止内存泄漏。 了解并合理使用线程池,不仅可以提高系统的并发性能,还能有效地管理和控制线程,提升...
当任务完成后,线程并不立即销毁,而是返回到线程池中等待下一次的任务分配。这种方式避免了频繁地创建和销毁线程所带来的开销,提高了系统性能。 描述中提到“可以作为参考”,暗示这个线程池实现可能是为了教学或...
6. **TemporarilyThread.cpp**:可能处理临时任务或一次性任务的线程类实现。 7. **IThreadExcute.cpp**:可能定义了一个接口类,规定了线程执行的任务的接口,使得线程池可以执行不同类型的任务。 8. **Sdate.cpp...
线程池是一种多线程处理形式,预先创建一定数量的线程,存放在池中,当需要执行任务时,无需每次都创建新线程,而是从池中取出一个线程来执行任务,任务完成后,线程返回线程池,等待下一次的任务分配。这种设计可以...
通过深入研究SmartThreadPool的源码,开发者不仅可以学习到线程池的设计与实现,还可以根据自身的需求进行二次开发,打造出更加符合业务场景的线程池解决方案。 总之,SmartThreadPool作为一款开源的高性能线程池...
可以用来安排在给定延迟后执行一次任务,或者周期性地执行任务。这对于需要定期维护或者清理的任务非常有用。例如,可以使用`scheduleAtFixedRate`或`scheduleWithFixedDelay`方法来实现定时任务。 4. `...
【项目介绍】基于C++17的简易线程池任务描述- 实现多线程安全的任务队列,线程池使用异步操作,提交(submit)使用与thread相同。- 内部利用完美转发获取可调用对象的函数签名,lambda与function包装任务,使用RAII...
其次,系统设定的采集频率为每5秒一次,这是根据实际应用场景和电表数据刷新频率综合考虑的结果。频繁的采集可能会增加网络负担,而采集间隔过长则可能导致数据丢失或延迟。通过定时任务(如...
- **优化线程池使用**:限制线程池最大线程数,确保任务能及时完成并释放线程。 - **审查同步代码**:检查并修复可能导致死锁或阻塞的代码段。 - **检查异步编程**:确保正确使用异步模型,避免不必要的同步等待...
`TemporarilyThread.cpp`可能表示一种临时线程的实现,这可能是为了处理一次性或者非持久的任务,例如初始化操作或者异常处理。 `ThreadManage.aps`和`ThreadManage.clw`这两个文件可能是工程文件,用于Visual ...
分页查询是指在查询数据时,将数据分成多个页面展示,而不是一次性返回所有数据。这种方式能够有效地减少单次查询的数据量,从而提高查询速度和用户体验。通常分页查询涉及到的关键参数包括:当前页码、每页显示记录...
数据入库时,通常会使用数据库的批处理功能,如文中调用的`stencDao.insertBatch(listBean)`,这允许一次提交多条记录,减少了与数据库交互的次数,从而提高性能。 在数据处理阶段,使用多线程技术来并发处理数据,...
当线程完成任务后,不会立即销毁,而是返回线程池等待下一次的任务分配,这样可以有效地减少线程创建和销毁的时间成本,提高系统响应速度。 ### 2. 线程池插件的安装与配置 安装线程池插件通常需要在MySQL源码编译...
5. 任务执行完毕后,线程返回到等待状态,等待下一次任务分配。 在Boost线程池库中,`thread_pool`类还提供了`stop()`方法来停止线程池,`join()`方法等待所有线程结束。此外,`thread_pool`类内部使用`condition_...