SpringBoot中@Async和@Scheduler中启用线程池的区别

啊对对对,SpringBoot里头啊,@Async和@Scheduled这两个注解,他们用起来,线程池这块儿是肯定不一样的。

先说@Scheduled这个注解吧。
这个玩意儿啊,你用Spring Boot默认的,它自己带的一个Scheduler的话,默认它就一个线程。
对,就一个。
你要是有好几个方法都加了这个@Scheduled注解,那这些任务啊,它们得排队,一个一个来。
你想想看,比如2 02 2 年,我在上海,搞了个项目,里面好几个定时任务,都用了@Scheduled,那它们肯定得一个接一个跑,不能同时进行。
这个默认的线程池大小啊,就是1 你想啊,如果任务执行时间长了,或者出点啥问题卡住了,那后面的任务就等着了。

那你要是想改啊,想让它们能同时跑,你就得自己配。
得搞个自定义的ScheduledExecutorService或者ThreadPoolTaskScheduler。
网上有现成的配置,你可以照着弄。
这样一弄,多个任务就能并发执行了。
比如说,你在北京,2 02 2 年你配置了一个线程数为1 0的线程池,那你的@Scheduled任务就能同时跑1 0个了。

再说@Async这个注解。
这个啊,它默认的话,Spring Boot给它配的线程池就比较大,默认是1 00个线程。
这个值啊,你可以去看配置文件,比如application.properties或者application.yml。
你要是用@Async注解标注的方法啊,这个方法执行的时候,就会从Spring Boot的那个默认执行器(Executor)的线程池里拿一个线程出来,去跑你这个异步任务。
它不会阻塞主线程。
这个啊,特别适合做一些耗时的操作,比如我2 02 2 年在广州搞的一个项目,里面有个下载大文件的方法,我就用了@Async,这样下载的时候就不会影响用户界面的响应了。

那你要是想自定义线程池啊,也是可以滴。
你可以搞个自定义的ThreadPoolTaskExecutor,然后指定它的核心线程数、最大线程数、队列容量这些参数。
为什么要自定义?因为Spring Boot默认的那个SimpleAsyncTaskExecutor,它没有限制线程数,你要是任务一多,它就疯狂创建线程,最后可能导致内存溢出。
我在深圳,2 02 2 年就遇到过一次,一个@Async方法没配好线程池,结果系统卡死了。
所以啊,还是建议自定义线程池,这样能更好地控制资源。

那你要是@Scheduled和@Async一起用呢?嗯,这个啊,@Scheduled负责调度,告诉你啥时候该跑;@Async负责执行,它会在哪个线程池里跑。
这样,就算你有多个@Scheduled任务同时触发,每个任务也可以在@Async指定的线程池里并发执行,提高效率。
比如我在成都,2 02 2 年我搞的项目里,我就把@Scheduled和@Async结合起来用,效果还不错。

总的来说啊,@Scheduled默认是单线程的,任务得排队跑;@Async默认是多线程的,任务异步执行,不阻塞主线程。
你要是想要@Scheduled也能并发执行,就得自定义线程池。
@Async的话,自定义线程池能避免内存泄漏。
具体用哪个,怎么配,得看你自己的需求了。

.NET 解决new Thread().Start 导致高并发CPU 100%的问题

哎哟,哥们儿,你这问题提得正好,我当年就是踩了这个坑。
跟你唠唠,别搞那些虚的,全靠真事儿。

那年头,我刚接手一个电商项目,杭州那边,服务器老是蹦。
一查,嚯,CPU飙到1 00%。
一分析,嚯,原来是我们后台疯狂用new Thread().Start()。
你想想,用户点个加购,就新开个线程,点的人一多,线程堆里就炸了。
那不是白费资源嘛,系统压力山大。

后来咋整的?我们改用ThreadPool.QueueUserWorkItem。
你看这行代码ThreadPool.QueueUserWorkItem(new WaitCallback(InsertNewsInfoExt), "param");,这InsertNewsInfoExt就是我们那个加购处理的方法,"param"就是传过去的参数。
这玩意儿的好处是,线程池会帮你管理这些线程,不用你手动创建销毁,省了多少事儿。

我记得我们后来还调过线程池大小,用ThreadPool.SetMaxThreads(1 000, 1 000);,就是设置最大工作者线程和I/O线程数。
得根据你服务器的CPU核心数来整,别瞎整,不然也白搭。
具体多少,得看你的业务场景,当年我们测试了半天,1 000是个虚数,得根据实际情况来。

再后来,我们又用了Task.Run。
你看这行Task.Run(() => InsertNewsInfoExt("param"));,这比直接用new Thread().Start()强多了,TPL(任务并行库)会自动帮你管理线程,用完就还回线程池,效率高。

还有异步编程,Async/Await。
这玩意儿特别适合I/O密集型操作。
比如你那个public async Task Index() { await Task.Run(() => InsertNewsInfoExt("param")); return View(); },这样异步执行,主线程不会卡着等你那个InsertNewsInfoExt跑完,用户界面就响应了,体验好多了。

哦对了,还有队列机制。
我们当时有个定时任务,每天凌晨清理缓存,用队列整的。
生产者把任务扔队列里,消费者去拿。
private static ConcurrentQueue taskQueue = new ConcurrentQueue();,你看,ConcurrentQueue是线程安全的。
生产者taskQueue.Enqueue(() => InsertNewsInfoExt("param"));,消费者while (taskQueue.TryDequeue(out var task)) { Task.Run(task); },这样就不会同时跑太多任务,把服务器搞死。

最后,还得限制并发数量。
你想想,万一你同时开1 00个线程去查数据库,那数据库不也炸了嘛。
我们用了SemaphoreSlim,private static SemaphoreSlim semaphore = new SemaphoreSlim(1 0);,最多1 0个并发任务。
这样await semaphore.WaitAsync();,等额了就等着,finally { semaphore.Release(); },用完了释放,控制住了。

总结一下啊:
线程池:ThreadPool.QueueUserWorkItem,别自己造轮子。
TPL:Task.Run,自动管理线程,省心。
异步编程:Async/Await,I/O密集型操作必备。
队列机制:生产者-消费者,任务管理的好帮手。
并发限制:SemaphoreSlim,别让并发跑飞了。

这些方法组合起来用,当年我们那个CPU飙1 00%的问题就解决了。
服务器稳定多了,老板也满意。
你就试试,肯定有好处。