我有10種方法搞定定時任務,10種!

      網友投稿 1532 2022-05-29

      前言

      最近有幾個讀者私信給我,問我他們的業務場景,要用什么樣的定時任務。確實,在不用的業務場景下要用不同的定時任務,其實我們的選擇還是挺多的。我今天給大家總結10種非常實用的定時任務,總有一種是適合你的。

      一. linux自帶的定時任務

      crontab

      不知道你有沒有遇到過這種場景:有時需要臨時統計線上的數據,然后導出到excel表格中。這種需求有時較為復雜,光靠寫sql語句是無法滿足需求的,這就需要寫java代碼了。然后將該程序打成一個jar包,在線上環境執行,最后將生成的excel文件下載到本地。

      為了減小對線上環境的影響,我們一般會選擇在凌晨1-2點,趁用戶量少的時候,執行統計程序。(其實凌晨4點左右,用戶才是最少的)

      由于時間太晚了,我們完全沒必要守在那里等執行結果,一個定時任務就能可以搞定。

      那么,這種情況用哪種定時任務更合適呢?

      答案是:linux系統的crontab。(不過也不排除有些項目沒部署在linux系統中)

      運行crontab -e,可以編輯定時器,然后加入如下命令:

      0?2?*?*?*?/usr/local/java/jdk1.8/bin/java?-jar?/data/app/tool.jar?>?/logs/tool.log?&

      就可以在每天凌晨2點,定時執行tool.jar程序,并且把日志輸出到tool.log文件中。當然你也可以把后面的執行java程序的命令寫成shell腳本,更方便維護。

      使用這種定時任務支持方便修改定時規則,有界面可以統一管理配置的各種定時腳本。

      crontab命令的基本格式如下:

      crontab?[參數]?[文件名]

      如果沒有指定文件名,則接收鍵盤上輸入的命令,并將它載入到crontab。

      參數功能對照表如下:

      以上參數,如果沒有使用-u指定用戶,則默認使用的當前用戶。

      通過crontab -e命令編輯文件內容,具體語法如下:

      [分]?[小時]?[日期]?[月]?[星期]?具體任務

      其中:

      分,表示多少分鐘,范圍:0-59

      小時,表示多少小時,范圍:0-23

      日期,表示具體在哪一天,范圍:1-31

      月,表示多少月,范圍:1-12

      星期,表示多少周,范圍:0-7,0和7都代表星期日

      還有一些特殊字符,比如:

      *代表如何時間,比如:*1*** 表示每天凌晨1點執行。

      /代表每隔多久執行一次,比如:*/5 **** 表示每隔5分鐘執行一次。

      ,代表支持多個,比如:10 7,9,12 *** 表示在每天的7、9、12點10分各執行一次。

      -代表支持一個范圍,比如:10 7-9 *** 表示在每天的7、8、9點10分各執行一次。

      此外,順便說一下crontab需要crond服務支持,crond是linux下用來周期地執行某種任務的一個守護進程,在安裝linux操作系統后,默認會安裝crond服務工具,且crond服務默認就是自啟動的。crond進程每分鐘會定期檢查是否有要執行的任務,如果有,則會自動執行該任務。

      可以通過以下命令操作相關服務:

      service?crond?status?//?查看運行狀態

      service?crond?start?//啟動服務

      service?crond?stop?//關閉服務

      service?crond?restart?//重啟服務

      service?crond?reload?//重新載入配置

      使用crontab的優缺點:

      優點:方便修改定時規則,支持一些較復雜的定時規則,通過文件可以統一管理配好的各種定時腳本。

      缺點:如果定時任務非常多,不太好找,而且必須要求操作系統是linux,否則無法執行。

      二. jdk自帶的定時任務

      1.Thread

      各位親愛的朋友,你沒看錯,Thread類真的能做定時任務。如果你看過一些定時任務框架的源碼,你最后會發現,它們的底層也會使用Thread類。

      實現這種定時任務的具體代碼如下:

      public static void init() {

      new Thread(() -> {

      while (true) {

      try {

      System.out.println("doSameThing");

      Thread.sleep(1000 * 60 * 5);

      } catch (Exception e) {

      log.error(e);

      }

      }

      }).start();

      }

      使用Thread類可以做最簡單的定時任務,在run方法中有個while的死循環(當然還有其他方式),執行我們自己的任務。有個需要特別注意的地方是,需要用try...catch捕獲異常,否則如果出現異常,就直接退出循環,下次將無法繼續執行了。

      這種方式做的定時任務,只能周期性執行,不能支持定時在某個時間點執行。

      此外,該線程可以定義成守護線程,在后臺默默執行就好。

      使用場景:比如項目中有時需要每隔10分鐘去下載某個文件,或者每隔5分鐘去讀取模板文件生成靜態html頁面等等,一些簡單的周期性任務場景。

      使用Thread類的優缺點:

      優點:這種定時任務非常簡單,學習成本低,容易入手,對于那些簡單的周期性任務,是個不錯的選擇。

      缺點:不支持指定某個時間點執行任務,不支持延遲執行等操作,功能過于單一,無法應對一些較為復雜的場景。

      2.Timer

      Timer類是jdk專門提供的定時器工具,用來在后臺線程計劃執行指定任務,在java.util包下,要跟TimerTask一起配合使用。

      Timer類其實是一個任務調度器,它里面包含了一個TimerThread線程,在這個線程中無限循環從TaskQueue中獲取TimerTask(該類實現了Runnable接口),調用其run方法,就能異步執行定時任務。我們需要繼承TimerTask類,實現它的run方法,在該方法中加上自己的業務邏輯。

      實現這種定時任務的具體代碼如下:

      public?class?TimerTest?{

      public?static?void?main(String[]?args)?{

      Timer?timer?=?new?Timer();

      timer.schedule(new?TimerTask()?{

      @Override

      public?void?run()?{

      System.out.println("doSomething");

      }

      },2000,1000);

      }

      }

      先實例化一個Timer類,然后調用它的schedule方法,在該方法中實例化TimerTask類,業務邏輯寫在run方法中。schedule方法最后的兩次參數分別表示:延遲時間 和 間隔時間,單位是毫秒。上面例子中,設置的定時任務是每隔1秒執行一次,延遲2秒執行。

      主要包含6個方法:

      schedule(TimerTask task, Date time), 指定任務task在指定時間time執行

      schedule(TimerTask task, long delay), 指定任務task在指定延遲delay后執行

      schedule(TimerTask task, Date firstTime,long period),指定任務task在指定時間firstTime執行后,進行重復固定延遲頻率peroid的執行

      我有10種方法搞定定時任務,10種!

      schedule(TimerTask task, long delay, long period), 指定任務task 在指定延遲delay 后,進行重復固定延遲頻率peroid的執行

      scheduleAtFixedRate(TimerTask task,Date firstTime,long period), 指定任務task在指定時間firstTime執行后,進行重復固定延遲頻率peroid的執行

      scheduleAtFixedRate(TimerTask task, long delay, long period), 指定任務task 在指定延遲delay 后,進行重復固定延遲頻率peroid的執行

      不過使用Timer實現定時任務有以下問題:

      由于Timer是單線程執行任務,如果其中一個任務耗時非常長,會影響其他任務的執行。

      如果TimerTask拋出RuntimeException,Timer會停止所有任務的運行。

      使用Timer類的優缺點:

      優點:非常方便實現多個周期性的定時任務,并且支持延遲執行,還支持在指定時間之后支持,功能還算強大。

      缺點:如果其中一個任務耗時非常長,會影響其他任務的執行。并且如果TimerTask拋出RuntimeException,Timer會停止所有任務的運行,所以阿里巴巴開發者規范中不建議使用它。

      3.ScheduledExecutorService

      ScheduledExecutorService是JDK1.5+版本引進的定時任務,該類位于java.util.concurrent并發包下。

      ScheduledExecutorService是基于多線程的,設計的初衷是為了解決Timer單線程執行,多個任務之間會互相影響的問題。

      它主要包含4個方法:

      schedule(Runnable command,long delay,TimeUnit unit),帶延遲時間的調度,只執行一次,調度之后可通過Future.get()阻塞直至任務執行完畢。

      schedule(Callable callable,long delay,TimeUnit unit),帶延遲時間的調度,只執行一次,調度之后可通過Future.get()阻塞直至任務執行完畢,并且可以獲取執行結果。

      scheduleAtFixedRate,表示以固定頻率執行的任務,如果當前任務耗時較多,超過定時周期period,則當前任務結束后會立即執行。

      scheduleWithFixedDelay,表示以固定延時執行任務,延時是相對當前任務結束為起點計算開始時間。

      實現這種定時任務的具體代碼如下:

      public?class?ScheduleExecutorTest?{

      public?static?void?main(String[]?args)?{

      ScheduledExecutorService?scheduledExecutorService?=?Executors.newScheduledThreadPool(5);

      scheduledExecutorService.scheduleAtFixedRate(()?->?{

      System.out.println("doSomething");

      },1000,1000,?TimeUnit.MILLISECONDS);

      }

      }

      調用ScheduledExecutorService類的scheduleAtFixedRate方法實現周期性任務,每隔1秒鐘執行一次,每次延遲1秒再執行。

      這種定時任務是阿里巴巴開發者規范中用來替代Timer類的方案,對于多線程執行周期性任務,是個不錯的選擇。

      ScheduledExecutorService的優缺點:

      優點:基于多線程的定時任務,多個任務之間不會相關影響,支持周期性的執行任務,并且帶延遲功能。

      缺點:不支持一些較復雜的定時規則。

      三. spring支持的定時任務

      1.spring task

      spring task是spring3以上版本自帶的定時任務,實現定時任務的功能時,需要引入spring-context包,目前它支持:xml 和 注解 兩種方式。

      1. 項目實戰

      由于xml方式太古老了,我們以springboot項目中注解方式為例。

      第一步,在pom.xml文件中引入spring-context相關依賴。

      org.springframework

      spring-context

      第二步,在springboot啟動類上加上@EnableScheduling注解。

      @EnableScheduling

      @SpringBootApplication

      public?class?Application?{

      public?static?void?main(String[]?args)?{

      new?SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);

      }

      }

      第三步,使用@Scheduled注解定義定時規則。

      @Service

      public?class?SpringTaskTest?{

      @Scheduled(cron?=?"${sue.spring.task.cron}")

      public?void?fun()?{

      System.out.println("doSomething");

      }

      }

      第四步,在applicationContext.properties文件中配置參數:

      sue.spring.task.cron=*/10?*?*?*?*??

      這樣就能每隔10秒執行一次fun方法了。

      2. cron規則

      spring4以上的版本中,cron表達式包含6個參數:

      [秒]?[分]?[時]?[日期]?[月]?[星期]

      還支持幾個常用的特殊符號:

      *:表示任何時間觸發任務

      ,:表示指定的時間觸發任務

      -:表示一段時間內觸發任務

      /:表示從哪一個時刻開始,每隔多長時間觸發一次任務。

      ?:表示用于月中的天和周中的天兩個子表達式,表示不指定值。

      cron表達式參數具體含義:

      秒,取值范圍:0-59,支持*、,、-、/。

      分,取值范圍:0-59,支持*、,、-、/。

      時,取值范圍:0-23,支持*、,、-、/。

      日期,取值范圍:1-31,支持*、,、-、/。比秒多了?,表示如果指定的星期觸發了,則配置的日期變成無效。

      月,取值范圍:1-12,支持*、,、-、/。

      星期,取值范圍:1~7,1代表星期天,6代表星期六,其他的以此類推。支持*、,、-、/、?。比秒多了?,表示如果指定的日期觸發了,則配置的星期變成無效。

      常見cron表達式使用舉例:

      0 0 0 1 * ? ?每月1號零點執行

      0 0 2 * * ? ?每天凌晨2點執行

      0 0 2 * * ? ?每天凌晨2點執行

      0 0/5 11 * * ? 每天11點-11點55分,每隔5分鐘執行一次

      0 0 18 ? * WED 每周三下午6點執行

      spring task先通過ScheduledAnnotationBeanPostProcessor類的processScheduled方法,解析和收集Scheduled注解中的參數,包含:cron表達式。

      然后在ScheduledTaskRegistrar類的afterPropertiesSet方法中,默認初始化一個單線程的ThreadPoolExecutor執行任務。

      對spring task感興趣的小伙伴,可以加我微信找我私聊。

      使用spring task的優缺點:

      優點:spring框架自帶的定時功能,springboot做了非常好的封裝,開啟和定義定時任務非常容易,支持復雜的cron表達式,可以滿足絕大多數單機版的業務場景。單個任務時,當前次的調度完成后,再執行下一次任務調度。

      缺點:默認單線程,如果前面的任務執行時間太長,對后面任務的執行有影響。不支持集群方式部署,不能做數據存儲型定時任務。

      2.spring quartz

      quartz是OpenSymphony開源組織在Job scheduling領域的開源項目,是由java開發的一個開源的任務日程管理系統。

      quartz能做什么?

      作業調度:調用各種框架的作業腳本,例如shell,hive等。

      定時任務:在某一預定的時刻,執行你想要執行的任務。

      架構圖如下:

      quartz包含的主要接口如下:

      Scheduler 代表調度容器,一個調度容器中可以注冊多個JobDetail和Trigger。

      Job 代表工作,即要執行的具體內容。

      JobDetail 代表具體的可執行的調度程序,Job是這個可執行程調度程序所要執行的內容。

      JobBuilder 用于定義或構建JobDetail實例。

      Trigger 代表調度觸發器,決定什么時候去調。

      TriggerBuilder 用于定義或構建觸發器。

      JobStore 用于存儲作業和任務調度期間的狀態。

      1. 項目實戰

      我們還是以springboot集成quartz為例。

      第一步,在pom.xml文件中引入quartz相關依賴。

      org.springframework.boot

      spring-boot-starter-quartz

      第二步,創建真正的定時任務執行類,該類繼承QuartzJobBean。

      public?class?QuartzTestJob?extends?QuartzJobBean?{

      @Override

      protected?void?executeInternal(JobExecutionContext?context)?throws?JobExecutionException?{

      String?userName?=?(String)?context.getJobDetail().getJobDataMap().get("userName");

      System.out.println("userName:"?+?userName);

      }

      }

      第三步,創建調度程序JobDetail和調度器Trigger。

      @Configuration

      public?class?QuartzConfig?{

      @Value("${sue.spring.quartz.cron}")

      private?String?testCron;

      /**

      *?創建定時任務

      */

      @Bean

      public?JobDetail?quartzTestDetail()?{

      JobDetail?jobDetail?=?JobBuilder.newJob(QuartzTestJob.class)

      .withIdentity("quartzTestDetail",?"QUARTZ_TEST")

      .usingJobData("userName",?"susan")

      .storeDurably()

      .build();

      return?jobDetail;

      }

      /**

      *?創建觸發器

      */

      @Bean

      public?Trigger?quartzTestJobTrigger()?{

      //每隔5秒執行一次

      CronScheduleBuilder?cronScheduleBuilder?=?CronScheduleBuilder.cronSchedule(testCron);

      //創建觸發器

      Trigger?trigger?=?TriggerBuilder.newTrigger()

      .forJob(quartzTestDetail())

      .withIdentity("quartzTestJobTrigger",?"QUARTZ_TEST_JOB_TRIGGER")

      .withSchedule(cronScheduleBuilder)

      .build();

      return?trigger;

      }

      }

      第四步,在applicationContext.properties文件中配置參數:

      sue.spring.quartz.cron=*/5?*?*?*?*??

      這樣就能每隔5秒執行一次QuartzTestJob類的executeInternal方法了。

      CronTrigger配置格式:

      [秒]?[分]?[小時]?[日]?[月]?[周]?[年]

      spring quartz跟spring task的cron表達式規則基本一致,只是spring4以上的版本去掉了后面的年,而quartz的CronTrigger的年是非必填的,這里我就不做過多介紹了。

      使用spring quartz的優缺點:

      優點:默認是多線程異步執行,單個任務時,在上一個調度未完成時,下一個調度時間到時,會另起一個線程開始新的調度,多個任務之間互不影響。支持復雜的cron表達式,它能被集群實例化,支持分布式部署。

      缺點:相對于spring task實現定時任務成本更高,需要手動配置QuartzJobBean、JobDetail和Trigger等。需要引入了第三方的quartz包,有一定的學習成本。不支持并行調度,不支持失敗處理策略和動態分片的策略等。

      四. 分布式定時任務

      1.xxl-job

      xxl-job是大眾點評(許雪里)開發的一個分布式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。現已開放源代碼并接入多家公司線上產品線,開箱即用。

      xxl-job框架對quartz進行了擴展,使用mysql數據庫存儲數據,并且內置jetty作為RPC服務調用。

      主要特點如下:

      有界面維護定時任務和觸發規則,非常容易管理。

      能動態啟動或停止任務

      支持彈性擴容縮容

      支持任務失敗報警

      支持動態分片

      支持故障轉移

      Rolling實時日志

      支持用戶和權限管理

      管理界面:

      整體架構圖如下:

      使用quartz架構圖如下:

      項目實戰

      xxl-admin管理后臺部署和mysql腳本執行等這些前期準備工作,我就不過多介紹了,有需求的朋友可以找我私聊,這些更偏向于運維的事情。

      假設前期工作已經OK了,接下來我們需要:

      第一步,在pom.xml文件中引入xxl-job相關依賴。

      com.xuxueli

      xxl-job-core

      第二步,在applicationContext.properties文件中配置參數:

      xxl.job.admin.address:?http://localhost:8088/xxl-job-admin/

      xxl.job.executor.appname:?xxl-job-executor-sample

      xxl.job.executor.port:?8888

      xxl.job.executor.logpath:?/data/applogs/xxl-job/

      第三步,創建HelloJobHandler類繼承IJobHandler類:

      @JobHandler(value?=?"helloJobHandler")

      @Component

      public?class?HelloJobHandler?extends?IJobHandler?{

      @Override

      public?ReturnT?execute(String?param)?{

      System.out.println("XXL-JOB,?Hello?World.");

      return?SUCCESS;

      }

      }

      這樣定時任務就配置好了。

      建議把定時任務單獨部署到另外一個服務中,跟api服務分開。根據我以往的經驗,job大部分情況下,會對數據做批量操作,如果操作的數據量太大,可能會對服務的內存和cpu資源造成一定的影響。

      使用xxl-job的優缺點:

      優點:有界面管理定時任務,支持彈性擴容縮容、動態分片、故障轉移、失敗報警等功能。它的功能非常強大,很多大廠在用,可以滿足絕大多數業務場景。

      缺點:和quartz一樣,通過數據庫分布式鎖,來控制任務不能重復執行。在任務非常多的情況下,有一些性能問題。

      2.elastic-job

      elastic-job是當當網開發的彈性分布式任務調度系統,功能豐富強大,采用zookeeper實現分布式協調,實現任務高可用以及分片。它是專門為高并發和復雜業務場景開發。

      elastic-job目前是apache的shardingsphere項目下的一個子項目,官網地址:http://shardingsphere.apache.org/elasticjob/。

      elastic-job在2.x之后,出了兩個產品線:Elastic-Job-Lite和Elastic-Job-Cloud,而我們一般使用Elastic-Job-Lite就能夠滿足需求。Elastic-Job-Lite定位為輕量級無中心化解決方案,使用jar包的形式提供分布式任務的協調服務,外部僅依賴于Zookeeper。。

      主要特點如下:

      分布式調度協調

      彈性擴容縮容

      失效轉移

      錯過執行作業重觸發

      作業分片一致性,保證同一分片在分布式環境中僅一個執行實例

      自診斷并修復分布式不穩定造成的問題

      支持并行調度

      整體架構圖:

      項目實戰

      第一步,在pom.xml文件中引入elastic-job相關依賴。

      com.dangdang

      elastic-job-lite-core

      com.dangdang

      elastic-job-lite-spring

      第二步,增加ZKConfig類,配置zookeeper:

      @Configuration

      @ConditionalOnExpression("'${zk.serverList}'.length()?>?0")

      public?class?ZKConfig?{

      @Bean

      public?ZookeeperRegistryCenter?registry(@Value("${zk.serverList}")?String?serverList,

      @Value("${zk.namespace}")?String?namespace)?{

      return?new?ZookeeperRegistryCenter(new?ZookeeperConfiguration(serverList,?namespace));

      }

      }

      第三步,定義一個類實現SimpleJob接口:

      public?class?TestJob?implements?SimpleJob?{

      @Override

      public?void?execute(ShardingContext?shardingContext){

      System.out.println("ShardingTotalCount:"+shardingContext.getShardingTotalCount());

      System.out.println("ShardingItem:"+shardingContext.getShardingItem());

      }

      }

      第四步,增加JobConfig配置任務:

      @Configuration

      public?class?JobConfig?{

      @Value("${sue.spring.elatisc.cron}")

      private?String?testCron;

      @Value("${sue.spring.elatisc.itemParameters}")

      private??String?shardingItemParameters;

      @Value("${sue.spring.elatisc.jobParameters}")

      private?String?jobParameters?=;

      @Value("${sue.spring.elatisc.shardingTotalCount}")

      private?int?shardingTotalCount;

      @Autowired

      private?ZookeeperRegistryCenter?registryCenter;

      @Bean

      public?SimpleJob?testJob()?{

      return?new?TestJob();

      }

      @Bean

      public?JobScheduler?simpleJobScheduler(final?SimpleJob?simpleJob)?{

      return?new?SpringJobScheduler(simpleJob,?registryCenter,?getConfiguration(simpleJob.getClass(),

      cron,?shardingTotalCount,?shardingItemParameters,?jobParameters));

      }

      private?geConfiguration?getConfiguration(Class?jobClass,String?cron,int?shardingTotalCount,String?shardingItemParameters,String?jobParameters)?{

      JobCoreConfiguration?simpleCoreConfig?=?JobCoreConfiguration.newBuilder(jobClass.getName(),?testCron,?shardingTotalCount).

      shardingItemParameters(shardingItemParameters).jobParameter(jobParameters).build();

      SimpleJobConfiguration?simpleJobConfig?=?new?SimpleJobConfiguration(simpleCoreConfig,?jobClass.getCanonicalName());

      LiteJobConfiguration?jobConfig?=?LiteJobConfiguration.newBuilder(simpleJobConfig).overwrite(true).build();

      return?jobConfig;

      }

      }

      其中:

      cron:cron表達式,定義觸發規則。

      shardingTotalCount:定義作業分片總數

      shardingItemParameters:定義分配項參數,一般用分片序列號和參數用等號分隔,多個鍵值對用逗號分隔,分片序列號從0開始,不可大于或等于作業分片總數。

      jobParameters:作業自定義參數

      第五步,在applicationContext.properties文件中配置參數:

      spring.application.name=elasticjobDemo

      zk.serverList=localhost:2181

      zk.namespace=elasticjobDemo

      sue.spring.elatisc.cron=0/5?*?*?*?*??

      sue.spring.elatisc.itemParameters=0=A,1=B,2=C,3=D

      sue.spring.elatisc.jobParameters=test

      sue.spring.elatisc.shardingTotalCount=4

      這樣定時任務就配置好了,創建定時任務的步驟,相對于xxl-job來說要繁瑣一些。

      使用elastic-job的優缺點:

      優點:支持分布式調度協調,支持分片,適合高并發,和一些業務相對來說較復雜的場景。

      缺點:需要依賴于zookeeper,實現定時任務相對于xxl-job要復雜一些,要對分片規則非常熟悉。

      3.其他分布式定時任務

      1. Saturn

      Saturn是唯品會開源的一個分布式任務調度平臺。取代傳統的Linux Cron/Spring Batch Job的方式,做到全域統一配置,統一監控,任務高可用以及分片并發處理。

      Saturn是在當當開源的Elastic-Job基礎上,結合各方需求和我們的實踐見解改良而成。使用案例:唯品會、酷狗音樂、新網銀行、海融易、航美在線、量富征信等。

      github地址:https://github.com/vipshop/Saturn/

      2. TBSchedule

      TBSchedule是阿里開發的一款分布式任務調度平臺,旨在將調度作業從業務系統中分離出來,降低或者是消除和業務系統的耦合度,進行高效異步任務處理。

      目前被廣泛應用在阿里巴巴、淘寶、支付寶、京東、聚美、汽車之家、國美等很多互聯網企業的流程調度系統中。

      github地址:https://github.com/taobao/TBSchedule

      老實說優秀的定時任務還是挺多的,不是說哪種定時任務牛逼我們就一定要用哪種,而是要根據實際業務需求選擇。每種定時任務都有優缺點,合理選擇既能滿足業務需求,又能避免資源浪費,才是上上策。當然在實際的業務場景,通常會多種定時任務一起配合使用。

      順便說一句,歡迎親愛的小伙伴們,找我一起聊聊:你用過哪些定時任務,遇到過哪些問題,以及如何解決問題的。如果有相關問題也可以問我。

      希望我們能夠共同進步,一起成長。

      干了九年開發,我干黃了90%的項目

      2021-12-04

      是的,Spring Boot 3.0 的第一個快照版本發布了

      2021-12-02

      Java如何校驗兩個文件內容是相同的?

      2021-12-01

      Linux 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:【轉載】深度學習簡介
      下一篇:網絡編程面試題
      相關文章
      亚洲AV福利天堂一区二区三 | 亚洲精品综合一二三区在线| MM1313亚洲国产精品| 亚洲白嫩在线观看| 亚洲色无码专区在线观看| 亚洲中文久久精品无码| 国产性爱在线观看亚洲黄色一级片| 婷婷综合缴情亚洲狠狠尤物| 一本久久综合亚洲鲁鲁五月天| 在线亚洲精品视频| 日韩亚洲翔田千里在线| 国产亚洲综合精品一区二区三区| 国产亚洲精品美女| 亚洲精品乱码久久久久久蜜桃| 亚洲日本中文字幕一区二区三区| 亚洲日本中文字幕一区二区三区| 久久亚洲中文字幕精品一区| 激情97综合亚洲色婷婷五| 在线观看亚洲精品福利片| 亚洲中文字幕在线第六区| 亚洲精品国偷自产在线| 亚洲AV无码久久| 亚洲精品免费观看| 亚洲欧洲视频在线观看| 亚洲av片不卡无码久久| 亚洲综合色丁香婷婷六月图片| 亚洲日韩亚洲另类激情文学| 中文字幕亚洲精品无码| 亚洲av无码一区二区三区天堂| 色窝窝亚洲AV网在线观看| 亚洲精品成人区在线观看| 77777亚洲午夜久久多人| 国产成A人亚洲精V品无码| 亚洲无码日韩精品第一页| AV在线亚洲男人的天堂| 亚洲爆乳无码专区| 亚洲精品视频观看| 亚洲狠狠成人综合网| 国产精品亚洲专区无码唯爱网| 亚洲区日韩区无码区| 国产v亚洲v天堂无码网站|