Schedule定時(shí)任務(wù)在分布式產(chǎn)生的問(wèn)題詳解
正文
定時(shí)任務(wù)的實(shí)現(xiàn)方式多種多樣,框架也是層出不窮。
本文所談及的是 SpringBoot 本身所帶有的@EnableScheduling 、 @Scheduled
實(shí)現(xiàn)定時(shí)任務(wù)的方式。
以及采用這種方式,在分布式調(diào)度中可能會(huì)出現(xiàn)的問(wèn)題,又針對(duì)為什么會(huì)發(fā)生這種問(wèn)題?又該如何解決,做出了一些敘述。
為了適合每個(gè)階段的讀者,我把前面測(cè)試的代碼都貼出來(lái)啦~
確保每一步都是有跡可循的,希望大家不要嫌啰嗦,感謝
一、搭建基本環(huán)境
基本依賴
<parent> <artifactId>spring-boot-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.7.2</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
創(chuàng)建個(gè)啟動(dòng)類及定時(shí)任務(wù)
@SpringBootApplication public class ApplicationScheduling { public static void main(String[] args) { SpringApplication.run(ApplicationScheduling.class, args); } }
/** * @description: * @author: Ning Zaichun * @date: 2022年09月06日 0:02 */ @Slf4j @Component @EnableScheduling public class ScheduleService { // 每五秒執(zhí)行一次,cron的表達(dá)式就不再多說(shuō)明了 @Scheduled(cron = "0/5 * * * * ? ") public void testSchedule() { log.info("當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>{}", Thread.currentThread().getId()); } }
二、問(wèn)題::執(zhí)行時(shí)間延遲和單線程執(zhí)行
按照上面代碼中給定的cron表達(dá)式@Scheduled(cron = "0/5 * * * * ? ")
每五秒執(zhí)行一次,那么最近五次的執(zhí)行結(jié)果應(yīng)當(dāng)為:
2022-09-06 00:21:10
2022-09-06 00:21:15
2022-09-06 00:21:20
2022-09-06 00:21:25
2022-09-06 00:21:30
如果定時(shí)任務(wù)中是執(zhí)行非??斓娜蝿?wù)的,時(shí)間非常非常短,確實(shí)不會(huì)有什么的延遲性。
上面代碼執(zhí)行結(jié)果:
2022-09-06 19:42:10.018 INFO 24496 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:42:15.015 INFO 24496 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:42:20.001 INFO 24496 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:42:25.005 INFO 24496 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:42:30.007 INFO 24496 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
如果說(shuō)從時(shí)間上來(lái)看,說(shuō)不上什么延遲性,但真實(shí)的業(yè)務(wù)場(chǎng)景中,業(yè)務(wù)的執(zhí)行時(shí)間可能遠(yuǎn)比這里時(shí)間長(zhǎng)。
我主動(dòng)讓線程睡上10秒,讓我們?cè)賮?lái)看看輸出結(jié)果是如何的吧
@Scheduled(cron = "0/5 * * * * ? ") public void testSchedule() { try { Thread.sleep(10000); log.info("當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>{}", Thread.currentThread().getId()); } catch (Exception e) { e.printStackTrace(); } }
輸出結(jié)果
2022-09-06 19:46:50.019 INFO 27236 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:47:05.024 INFO 27236 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:47:20.016 INFO 27236 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:47:35.005 INFO 27236 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 19:47:50.006 INFO 27236 --- [ scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
請(qǐng)注意兩個(gè)問(wèn)題:
- 執(zhí)行時(shí)間延遲:從時(shí)間上可以明顯看出,不再是每五秒執(zhí)行一次,執(zhí)行時(shí)間延遲很多,造成任務(wù)的
- 單線程執(zhí)行:從始至終都只有一個(gè)線程在執(zhí)行任務(wù),造成任務(wù)的堵塞.
三、為什么會(huì)出現(xiàn)上述問(wèn)題?
問(wèn)題的根本:線程阻塞式執(zhí)行,執(zhí)行任務(wù)線程數(shù)量過(guò)少。
那到底是為什么呢?
回到啟動(dòng)類上,我們?cè)趩?dòng)上標(biāo)明了一個(gè)@EnableScheduling
注解。
大家在看到諸如@Enablexxxx
這樣的注解的時(shí)候,就要知道它一定有一個(gè)xxxxxAutoConfiguration
的自動(dòng)裝配的類。
@EnableScheduling
也不例外,它的自動(dòng)裝配的類是TaskSchedulingAutoConfiguration
。
我們來(lái)看看它到底做了一些什么設(shè)置?我們?nèi)绾涡薷模?/p>
@ConditionalOnClass(ThreadPoolTaskScheduler.class) @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(TaskSchedulingProperties.class) @AutoConfigureAfter(TaskExecutionAutoConfiguration.class) public class TaskSchedulingAutoConfiguration { @Bean @ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME) @ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class }) public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { return builder.build(); } // ...... }
可以看到它也是構(gòu)造了一個(gè) 線程池注入到Spring 中
從build()
調(diào)用繼續(xù)看下去,
public ThreadPoolTaskScheduler build() { return configure(new ThreadPoolTaskScheduler()); }
ThreadPoolTaskScheduler
中,給定的線程池的核心參數(shù)就為1,這也表明了之前為什么只有一條線程在執(zhí)行任務(wù)。private volatile int poolSize = 1;
這一段是分開的用代碼不好展示,我用圖片標(biāo)明出來(lái)。
主要邏輯在這里,創(chuàng)建線程池的時(shí)候,只使用了三個(gè)參數(shù),剩下的都是使用ScheduledExecutorService
的默認(rèn)的參數(shù)
protected ScheduledExecutorService createExecutor( int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler)
而這默認(rèn)參數(shù)是不行的,生產(chǎn)環(huán)境的大坑,阿里的 Java 開發(fā)手冊(cè)中也明確規(guī)定,要手動(dòng)創(chuàng)建線程池,并給定合適的參數(shù)值~是為什么呢?
因?yàn)槟J(rèn)的線程池中, 池中允許的最大線程數(shù)和最大任務(wù)等待隊(duì)列都是Integer.MAX_VALUE
.
大家都懂的,如果使用這玩意,只要出了問(wèn)題,必定掛~
configure(new ThreadPoolTaskScheduler())
這里就是構(gòu)造,略過(guò)~
如果已經(jīng)較為熟悉SpringBoot的朋友,現(xiàn)在已然明白解決當(dāng)前問(wèn)題的方式~
四、解決方式
1、@EnableConfigurationProperties(TaskSchedulingProperties.class)
,自動(dòng)裝配類通常也都會(huì)對(duì)應(yīng)有個(gè)xxxxProperties
文件滴,TaskSchedulingProperties
也確實(shí)可以配置核心線程數(shù)等基本參數(shù),但是無(wú)法配置線程池中最大的線程數(shù)量和等待隊(duì)列數(shù)量,這種方式還是不合適的。
2、可以手動(dòng)異步編排,交給某個(gè)線程池來(lái)執(zhí)行。
3、將定時(shí)任務(wù)加上異步注解@Async
,將其改為異步的定時(shí)任務(wù),另外自定義一個(gè)系統(tǒng)通用的線程池,讓異步任務(wù)使用該線程執(zhí)行任務(wù)~
我們分別針對(duì)上述三種方式來(lái)實(shí)現(xiàn)一遍
4.1、修改配置文件
可以配置的就下面幾項(xiàng)~
spring: task: scheduling: thread-name-prefix: nzc-schedule- #線程名前綴 pool: size: 10 #核心線程數(shù) # shutdown: # await-termination: true #執(zhí)行程序是否應(yīng)等待計(jì)劃任務(wù)在關(guān)機(jī)時(shí)完成。 # await-termination-period: #執(zhí)行程序應(yīng)等待剩余任務(wù)完成的最長(zhǎng)時(shí)間。
測(cè)試結(jié)果:
2022-09-06 20:49:15.015 INFO 7852 --- [ nzc-schedule-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 20:49:30.004 INFO 7852 --- [ nzc-schedule-2] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>66
2022-09-06 20:49:45.024 INFO 7852 --- [ nzc-schedule-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>64
2022-09-06 20:50:00.025 INFO 7852 --- [ nzc-schedule-3] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>67
2022-09-06 20:50:15.023 INFO 7852 --- [ nzc-schedule-2] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>66
2022-09-06 20:50:30.008 INFO 7852 --- [ nzc-schedule-4] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>68
請(qǐng)注意:這里的配置并非是一定生效的,修改后有可能成功,有可能失敗,具體原因未知,但這一點(diǎn)是真實(shí)存在的。
不過(guò)從執(zhí)行結(jié)果中可以看出,這里的執(zhí)行的線程不再是孤單單的一個(gè)。
4.2、執(zhí)行邏輯改為異步執(zhí)行
首先我們先向Spring中注入一個(gè)我們自己編寫的線程池,參數(shù)自己設(shè)置即可,我這里比較隨意。
@Configuration public class MyTheadPoolConfig { @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //設(shè)置核心線程數(shù) executor.setCorePoolSize(10); //設(shè)置最大線程數(shù) executor.setMaxPoolSize(20); //緩沖隊(duì)列200:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列 executor.setQueueCapacity(200); //線程活路時(shí)間 60 秒 executor.setKeepAliveSeconds(60); //線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池 // 這里我繼續(xù)沿用 scheduling 默認(rèn)的線程名前綴 executor.setThreadNamePrefix("nzc-create-scheduling-"); //設(shè)置拒絕策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; } }
然后在定時(shí)任務(wù)這里注入進(jìn)去:
/** * @description: * @author: Ning Zaichun * @date: 2022年09月06日 0:02 */ @Slf4j @Component @EnableScheduling public class ScheduleService { @Autowired TaskExecutor taskExecutor; @Scheduled(cron = "0/5 * * * * ? ") public void testSchedule() { CompletableFuture.runAsync(()->{ try { Thread.sleep(10000); log.info("當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>{}", Thread.currentThread().getId()); } catch (Exception e) { e.printStackTrace(); } },taskExecutor); } }
測(cè)試結(jié)果:
2022-09-06 21:00:00.019 INFO 18356 --- [te-scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>66
2022-09-06 21:00:05.022 INFO 18356 --- [te-scheduling-2] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>67
2022-09-06 21:00:10.013 INFO 18356 --- [te-scheduling-3] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>68
2022-09-06 21:00:15.020 INFO 18356 --- [te-scheduling-4] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>69
2022-09-06 21:00:20.026 INFO 18356 --- [te-scheduling-5] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>70
可以看到雖然業(yè)務(wù)執(zhí)行時(shí)間比較長(zhǎng),但是木有再出現(xiàn),延遲執(zhí)行定時(shí)任務(wù)的情況。
4.3、異步定時(shí)任務(wù)
異步定時(shí)任務(wù)其實(shí)和上面的方式原理是一樣的,不過(guò)實(shí)現(xiàn)稍稍不同罷了。
在定時(shí)任務(wù)的類上再加一個(gè)@EnableAsync
注解,給方法添加一個(gè)@Async
即可。
不過(guò)一般@Async
都會(huì)指定線程池,比如寫成這樣@Async(value = "taskExecutor")
,
/** * @description: * @author: Ning Zaichun * @date: 2022年09月06日 0:02 */ @Slf4j @Component @EnableAsync @EnableScheduling public class ScheduleService { @Autowired TaskExecutor taskExecutor; @Async(value = "taskExecutor") @Scheduled(cron = "0/5 * * * * ? ") public void testSchedule() { try { Thread.sleep(10000); log.info("當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>{}", Thread.currentThread().getId()); } catch (Exception e) { e.printStackTrace(); } } }
執(zhí)行結(jié)果:
2022-09-06 21:10:15.022 INFO 22760 --- [zc-scheduling-1] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>66
2022-09-06 21:10:20.021 INFO 22760 --- [zc-scheduling-2] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>67
2022-09-06 21:10:25.007 INFO 22760 --- [zc-scheduling-3] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>68
2022-09-06 21:10:30.020 INFO 22760 --- [zc-scheduling-4] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>69
2022-09-06 21:10:35.007 INFO 22760 --- [zc-scheduling-5] com.nzc.service.ScheduleService : 當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>70
結(jié)果顯而易見(jiàn)是可行的啦~
分析:
@EnableAsync
注解相應(yīng)的也有一個(gè)自動(dòng)裝配類為TaskExecutionAutoConfiguration
也有一個(gè)TaskExecutionProperties
配置類,可以在yml文件中對(duì)參數(shù)進(jìn)行設(shè)置,這里的話是可以配置線程池最大存活數(shù)量的。
它的默認(rèn)核心線程數(shù)為8,這里我不再進(jìn)行演示了,同時(shí)它的線程池中最大存活數(shù)量以及任務(wù)等待數(shù)量也都為Integer.MAX_VALUE
,這也是不建議大家使用默認(rèn)線程池的原因。
4.4、小結(jié)
/** * 定時(shí)任務(wù) * 1、@EnableScheduling 開啟定時(shí)任務(wù) * 2、@Scheduled開啟一個(gè)定時(shí)任務(wù) * 3、自動(dòng)裝配類 TaskSchedulingAutoConfiguration * * 異步任務(wù) * 1、@EnableAsync:開啟異步任務(wù) * 2、@Async:給希望異步執(zhí)行的方法標(biāo)注 * 3、自動(dòng)裝配類 TaskExecutionAutoConfiguration */
實(shí)現(xiàn)方式雖不同,但從效率而言,并無(wú)太大區(qū)別,覺(jué)得那種合適使用那種便可。
不過(guò)總結(jié)起來(lái),考查的都是對(duì)線程池的理解,對(duì)于線程池的了解是真的非常重要的,也很有用處。
五、分布式下的思考
針對(duì)上述情況而言,這些解決方法在不引入第三包的情況下是足以應(yīng)付大部分情況了。
定時(shí)框架的實(shí)現(xiàn)有許多方式,在此并非打算討論這個(gè)。
在單體項(xiàng)目中,也許上面的問(wèn)題是解決了,但是站在分布式的情況下考慮,就并非是安全的了。
當(dāng)多個(gè)項(xiàng)目在同時(shí)運(yùn)行,那么必然會(huì)有多個(gè)項(xiàng)目同時(shí)這段代碼。
思考:并發(fā)執(zhí)行
如果一個(gè)定時(shí)任務(wù)同時(shí)在多個(gè)機(jī)器中運(yùn)行,會(huì)產(chǎn)生怎么樣的問(wèn)題?
假如這個(gè)定時(shí)任務(wù)是收集某個(gè)信息,發(fā)送給消息隊(duì)列,如果多臺(tái)機(jī)器同時(shí)執(zhí)行,同時(shí)給消息隊(duì)列發(fā)送信息,那么必然導(dǎo)致之后產(chǎn)生一系列的臟數(shù)據(jù)。這是非常不可靠的
解決方式:分布式鎖
很簡(jiǎn)單也不簡(jiǎn)單,加分布式鎖~ 或者是用一些分布式調(diào)度的框架
如使用XXL-JOB實(shí)現(xiàn),或者是其他的定時(shí)任務(wù)框架。
大家在執(zhí)行這個(gè)定時(shí)任務(wù)之前,先去獲取一把分布式鎖,獲取到了就執(zhí)行,獲取不到就直接結(jié)束。
我這里使用的是 redission
,因?yàn)榉奖?,打算寫分布式鎖的文章,還在準(zhǔn)備當(dāng)中。
redission
官方文檔,我覺(jué)得應(yīng)當(dāng)算是比較友好的文檔了哈哈
加入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.17.6</version> </dependency>
按照文檔說(shuō)的,編寫配置類,注入 RedissonClient
,redisson的全部操作都是基于此。
/** * @description: * @author: Ning Zaichun * @date: 2022年09月06日 9:31 */ @Configuration public class MyRedissonConfig { /** * 所有對(duì)Redisson的使用都是通過(guò)RedissonClient * @return * @throws IOException */ @Bean(destroyMethod="shutdown") public RedissonClient redissonClient() throws IOException { //1、創(chuàng)建配置 Config config = new Config(); // 這里規(guī)定要用 redis://+IP地址 config.useSingleServer().setAddress("redis://xxxxx:6379").setPassword("000415"); // 有密碼就寫密碼~ 木有不用寫~ //2、根據(jù)Config創(chuàng)建出RedissonClient實(shí)例 //Redis url should start with redis:// or rediss:// RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }
修改定時(shí)任務(wù):
/** * @description: * @author: Ning Zaichun * @date: 2022年09月06日 0:02 */ @Slf4j @Component @EnableAsync @EnableScheduling public class ScheduleService { @Autowired TaskExecutor taskExecutor; @Autowired RedissonClient redissonClient; private final String SCHEDULE_LOCK = "schedule:lock"; @Async(value = "taskExecutor") @Scheduled(cron = "0/5 * * * * ? ") public void testSchedule() { //分布式鎖 RLock lock = redissonClient.getLock(SCHEDULE_LOCK); try { //加鎖 10 為時(shí)間,加上時(shí)間 默認(rèn)會(huì)去掉 redisson 的看門狗機(jī)制(即自動(dòng)續(xù)鎖機(jī)制) lock.lock(10, TimeUnit.SECONDS); Thread.sleep(10000); log.info("當(dāng)前執(zhí)行任務(wù)的線程號(hào)ID===>{}", Thread.currentThread().getId()); } catch (Exception e) { e.printStackTrace(); } finally { // 一定要記得解鎖~ lock.unlock(); } } }
這里只是給出個(gè)大概的實(shí)現(xiàn),實(shí)際上還是可以優(yōu)化的,比如在給定一個(gè)flag
,在獲取鎖之前判斷。如果有人搶到鎖,就修改這個(gè)值,之后的請(qǐng)求,判斷這個(gè)flag
,如果不是默認(rèn)的值,則直接結(jié)束任務(wù)等等。
思考:繼續(xù)往深處思考,在分布式情況下如果一個(gè)定時(shí)任務(wù)搶到鎖,但是它在執(zhí)行業(yè)務(wù)過(guò)程中失敗或者是宕機(jī)了,這又該如何處理呢?如何補(bǔ)償呢?
個(gè)人思考:
失敗還比較好說(shuō),我們可以直接try{}catch(){}中進(jìn)行通知告警,及時(shí)檢查出問(wèn)題。
如果是掛了,我還沒(méi)想好怎么做。
后記
但實(shí)際上,我所闡述的這種方式,只能說(shuō)適用于簡(jiǎn)單的單體項(xiàng)目,一旦牽扯到動(dòng)態(tài)定時(shí)任務(wù),使用這種方式就不再那么方便了。
大部分都是使用定時(shí)任務(wù)框架集成了,尤其是分布式調(diào)度遠(yuǎn)比單體項(xiàng)目需要考慮多的多。
以上就是Schedule定時(shí)任務(wù)在分布式產(chǎn)生的問(wèn)題詳解的詳細(xì)內(nèi)容,更多關(guān)于Schedule定時(shí)任務(wù)分布式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis動(dòng)態(tài)SQL?foreach批量操作方法
這篇文章主要介紹了Mybatis動(dòng)態(tài)SQL?foreach批量操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03Rabbitmq延遲隊(duì)列實(shí)現(xiàn)定時(shí)任務(wù)的方法
這篇文章主要介紹了Rabbitmq延遲隊(duì)列實(shí)現(xiàn)定時(shí)任務(wù),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Java實(shí)現(xiàn)bmp和jpeg圖片格式互轉(zhuǎn)
本文主要介紹了Java實(shí)現(xiàn)bmp和jpeg圖片格式互轉(zhuǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04圖解Java經(jīng)典算法快速排序的原理與實(shí)現(xiàn)
快速排序是基于二分的思想,對(duì)冒泡排序的一種改進(jìn)。主要思想是確立一個(gè)基數(shù),將小于基數(shù)的數(shù)放到基數(shù)左邊,大于基數(shù)的數(shù)字放到基數(shù)的右邊,然后在對(duì)這兩部分進(jìn)一步排序,從而實(shí)現(xiàn)對(duì)數(shù)組的排序2022-09-09Java的內(nèi)存區(qū)域與內(nèi)存溢出異常你了解嗎
這篇文章主要為大家詳細(xì)介紹了Java的內(nèi)存區(qū)域與內(nèi)存溢出異常,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03簡(jiǎn)單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別
這篇文章主要介紹了簡(jiǎn)單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Java中遍歷集合的并發(fā)修改異常解決方案實(shí)例代碼
當(dāng)你遍歷集合的同時(shí),又往集合中添加或者刪除元素,就可能報(bào)并發(fā)修改異常,下面這篇文章主要給大家介紹了關(guān)于Java中遍歷集合的并發(fā)修改異常解決方案的相關(guān)資料,需要的朋友可以參考下2022-12-12Java泛型與數(shù)據(jù)庫(kù)應(yīng)用實(shí)例詳解
這篇文章主要介紹了Java泛型與數(shù)據(jù)庫(kù)應(yīng)用,結(jié)合實(shí)例形式詳細(xì)分析了java繼承泛型類實(shí)現(xiàn)增刪改查操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-08-08使用Java將字符串在ISO-8859-1和UTF-8之間相互轉(zhuǎn)換
大家都知道在一些情況下,我們需要特殊的編碼格式,如:UTF-8,但是系統(tǒng)默認(rèn)的編碼為ISO-8859-1,遇到這個(gè)問(wèn)題,該如何對(duì)字符串進(jìn)行兩個(gè)編碼的轉(zhuǎn)換呢,下面小編給大家分享下java中如何在ISO-8859-1和UTF-8之間相互轉(zhuǎn)換,感興趣的朋友一起看看吧2021-12-12