spring boot(五)非同步呼叫

謎一樣的Coder發表於2018-08-27

前言

spring boot中呼叫非同步並沒有想象中的那麼複雜,之前在實習過程中已經遇到了幾次非同步呼叫的需求,自己都沒能順利弄下來,這裡正好來一個總結

建立定時任務

有時候系統中需要定時做一些任務,實現定時任務也沒有想象中的複雜(畢竟只是實現hello world)

1、建立定時任務實現類

package com.learn.springbootScheduled.util;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

	private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:SS");
	
	@Scheduled(fixedRate=5000)
	public void reportCurrentTime() {
		System.out.println("現在時間:"+dateFormat.format(new Date(System.currentTimeMillis())));
	}
	
}

標記Component註解,將類交給spring管理。在方法上打上@Scheduled標籤,該註解常用有如下幾個屬性。

註解示例 作用
@Scheduled(fixedRate=5000) 上一次開始執行時間之後5秒再執行
@Scheduled(fixedDelay=5000) 上一次執行完畢時間點之後5秒再執行
@Scheduled(initialDelay=1000,fixedRate=5000)

第一次延遲1秒後執行,之後按fixedRate的規則每5秒執行一次

@Scheduled(cron="*/5*****") 通過cron表示式定義規則

2、在主載入類上新增@EnableScheduling註解

@SpringBootApplication
@EnableScheduling
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

測試結果

 @Asyn實現非同步

spring boot中實現非同步操作也是非常容易的,比起之前什麼的master-work模式,什麼實現runnable介面,這個,已經非常簡潔了,廢話不多說,直接上示例,畢竟hello world級別的示例異常簡單。

ps:關於同步和非同步的區別,這裡我就不解釋了。

spring boot中我們只需要在方法上打上@Asyn標籤就能實現非同步操作。

非同步呼叫(非回撥)

1、編寫非同步邏輯實現類

package com.learn.springbootAsync.utils;

import java.util.Random;
import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment: 非同步功能函式
 */
@Component
public class Task {

	public static Random random = new Random();

	@Async
	public void doTaskOne() throws InterruptedException {
		System.out.println("開始做任務一");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
	}

	@Async
	public void doTaskTwo() throws InterruptedException {
		System.out.println("開始做任務二");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
	}

	@Async
	public void doTaskThree() throws InterruptedException {
		System.out.println("開始做任務三");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
	}
}

2、主載入類中加上@EnableAsync註解

@SpringBootApplication
@EnableAsync
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

3、測試程式碼

@Test
public void test() throws Exception {
	task.doTaskOne();
	task.doTaskTwo();
	task.doTaskThree();
	//主執行緒暫停
	Thread.sleep(1000);
}

測試結果:

每次執行,任務完成先後順序會不同。

@Async註解不能修飾靜態方法,否則不會生效。

非同步回撥

所謂的回撥就是主執行緒需要獲取非同步執行緒的返回結果,這個詳細內容後面需要參考Java 高併發的相關書籍(之前學習的,都他媽忘光了)。

1、回撥執行緒類

package com.learn.springbootAsync.utils;

import java.util.Random;
import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment: 非同步功能函式
 */
@Component
public class TaskFuture {

	public static Random random = new Random();

	@Async
	public Future<String> doTaskOne() throws InterruptedException {
		System.out.println("開始做任務一");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任務一完成");
	}

	@Async
	public Future<String> doTaskTwo() throws InterruptedException {
		System.out.println("開始做任務二");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任務二完成");
	}

	@Async
	public Future<String> doTaskThree() throws InterruptedException {
		System.out.println("開始做任務三");
		long start = System.currentTimeMillis();
		Thread.sleep(random.nextInt(1000));
		long end = System.currentTimeMillis();
		System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
		return new AsyncResult<String>("任務三完成");
	}
}

2、主載入類中同樣需要@EnableAsync註解

3、測試程式碼

	@Test
	public void testFuture() throws Exception {
		long start = System.currentTimeMillis();

		Future<String> task1 = taskFuture.doTaskOne();
		Future<String> task2 = taskFuture.doTaskTwo();
		Future<String> task3 = taskFuture.doTaskThree();

		while(true) {
			if(task1.isDone() && task2.isDone() && task3.isDone()) {
				// 三個任務都呼叫完成,退出迴圈等待
				System.out.println("回撥執行緒已經全部執行結束");
				break;
			}
			
			System.out.println("主執行緒正在進行其他操作");
			Thread.sleep(1000);
		}
		long end = System.currentTimeMillis();
		System.out.println("任務全部完成,總耗時:" + (end - start) + "毫秒");
	}

測試結果:

其中的完成任務X語句就是執行緒回撥的結果 ,耗時統計就是主執行緒完成的工作,根據這個模板,主執行緒在完成呼叫其他執行緒的時候,還可以完成自己的一些邏輯。

使用自定義的執行緒池

spring boot中使用自定義的執行緒池,關於執行緒池的介紹,建議參考《Java 併發程式設計實戰》一書

1、首先在spring boot主類中定義一個執行緒池

package com.learn.springbootThreadPoolSelf;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
	
	@EnableAsync
	@Configuration
	class TaskPoolConfiguration{
		
		@Bean("taskExecutor")
		public Executor taskExecutor() {
			ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(200);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("taskExecutor-");
            executor.setAwaitTerminationSeconds(60);
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
		}
	}
}

這裡一大堆相關執行緒池屬性的設定,還是有必要了解的,在前面推薦的書籍中都有介紹,後面的部落格會總結到這一步,這裡暫時忽略相關屬性。

2、@Async中指定執行緒池名稱

package com.learn.springbootThreadPoolSelf.util;

import java.util.Random;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 
 * @author liman
 * @createtime 2018年8月27日
 * @contract 15528212893
 * @comment:
 *
 */
@Component
public class TaskThreadPool {
	public static Random random = new Random();

    @Async("taskExecutor")
    public void doTaskOne() throws Exception {
    	System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任務一,耗時:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public void doTaskTwo() throws Exception {
    	System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任務二,耗時:" + (end - start) + "毫秒");
    }
    
    @Async("taskExecutor")
    public void doTaskThree() throws Exception {
    	System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(1000));
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 完成任務三,耗時:" + (end - start) + "毫秒");
    }
}

@Async中指定了之前設定的執行緒池名稱。這些執行緒邏輯程式碼會自動加入到執行緒池中。

3、測試程式碼

@Test
public void test() throws Exception {

    task.doTaskOne();
    task.doTaskTwo();
    task.doTaskThree();

    Thread.currentThread().join();
    Thread.sleep(2000);
}

執行結果:

 執行緒名稱為設定的執行緒名,說明任務已經放到指定的執行緒池中進行執行。

總結

總體來說,到目前為止,5篇spring boot系列的部落格,都只是存在helloworld級別,對實際開發有一定參考,每篇部落格都有些不足,對於一些稍微麻煩的示例,都沒有進行深入實現。這五篇部落格也是每天在工作之餘完成的,沒有更多的時間進行修飾。後續依舊會總結spring boot的其他功能。目前這5篇基本總結了spring boot中比較常用的功能。

相關文章