您好,登錄后才能下訂單哦!
前言
Springt通過任務(wù)執(zhí)行器(TaskExecutor)來實(shí)現(xiàn)多線程和并發(fā)編程。使用ThreadPoolTaskExecutor可實(shí)現(xiàn)一個基于線程池的TaskExecutor。而實(shí)際開發(fā)中任務(wù)一般是非阻礙的,即異步的,所以我們要在配置類中通過@EnableAsync 開啟對異步任務(wù)的支持,并通過實(shí)際執(zhí)行Bean的方法中使用@Async注解來聲明其是一個異步任務(wù)。
基于springboot的多線程程序開發(fā)過程中,由于本身也需要注入spring容器進(jìn)行管理,才能發(fā)揮springboot的優(yōu)勢。所以這篇文字主要用來記錄開發(fā)中兩者結(jié)合時需要注意的一些事項(xiàng)。
注意事項(xiàng)
第一步我們把線程類的實(shí)例注入sping容器進(jìn)行管理
@Configuration @SpringBootApplication @Import({ThreadConfig.class}) public class ThreadApp implements CommandLineRunner { public static void main(String[] args) throws Exception { ApplicationContext app = SpringApplication.run(ThreadApp .class, args); //這里主要保存上下文對象實(shí)例,需要加上。SpringBootUtils類網(wǎng)上很多,可以自己搜下 SpringBootUtils.setApplicationContext(app); } //access command line arguments @Override public void run(String... args) throws Exception { //do something } } //ComponentScan注解會掃描com.demo.thead下,也就是多線程類所在的包下的文件 @Configuration @ComponentScan(basePackages = { "com.demo.thread"}) public class ThreadConfig{ }
這里使用springboot @Import 注解,把ThreadConfig里掃描到的包中帶注解的示例,如@Component等注入到spring容器當(dāng)中.
然后是線程的啟動,這里在我的業(yè)務(wù)場景中有兩種情況:
1、程序運(yùn)行時,自動啟動;
這在一般的可執(zhí)行程序里面,當(dāng)然可以直接在main函數(shù)里執(zhí)行通過代碼啟動線程。但在springboot中,我們可以使用@PostConstruct注解的方式,讓已經(jīng)注入bean容器的線程對象自啟動
@Component public class demoThread extends Thread { //注意這里,如果你沒有實(shí)現(xiàn)把多線程類的實(shí)例注入到spring容器中,這里你是無法拿到其他自動裝配的對象實(shí)例的的,這也是我們第一步的意義所在。 @Autowired private XxxService xxxService; @PostConstruct public void start() { super.start(); } public void run() { // Ok,在這里你就可以實(shí)現(xiàn)線程要實(shí)現(xiàn)的功能邏輯了,自然也可以直接使用裝配好的sevice對象實(shí)例。 } }
2、在程序中,需要開啟線程時啟動,比如在從kafka接收數(shù)據(jù),開啟線程處理,當(dāng)然這種情況下也需要通過第一步,把線程類實(shí)例注入到sping容器中
private TaskThread thread; private ExecutorService taskPool= new ThreadPoolExecutor( 5, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy()); @KafkaListener(topics = "xxTopic") public void receive(ConsumerRecord<Object, Object> consumerRecord) { JSONObject json = JSON.parseObject(consumerRecord.value().toString()); //通過SpringBootUtils獲取線程類的實(shí)例 thread = SpringBootUtils.getBean(TaskThread.class); //啟動線程 //new Thread(thread).start() ; //向線程對象里傳值 thread.init(i); //放入線程池執(zhí)行 taskPool.execute(thread); }
//注意這里是否添加@Scope("prototype")注解 @Component @Scope("prototype") public class TaskThread implements Runnable{ protected int value=0; @Autowired private XxxService xxxService; //ThreadLocal 對象,單例模式下可以保證成員變量的線程安全和獨(dú)立性。 public ThreadLocal<Integer> valueLocal = new ThreadLocal < Integer > () { @Override protected Integer initialValue() { return 0; } }; protected static final Logger LOG = LoggerFactory.getLogger(GpsTaskThread.class); @Override public final void run() { try { LOG.info(value+""); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void init(int Value) { this.value=Value; } }
在這里我們需要注意,TaskThread這個線程類在spirngboot中是否要添加@Scope("prototype")
注解設(shè)置為多例模式還是默認(rèn)單例模式。
在單例模式下SpringBootUtils.getBean(TaskThread.class)
每次返回的都是同一個對象,雖然不需要每次都創(chuàng)建新的對象,但無法保證成員變量的線程安全,也就是說在線程池中的執(zhí)行的線程,它們的value值是共享的。而多例模式下,由于每次創(chuàng)建的都是一個新的線程對象,則不存在上述問題。
所以在這里請大家注意無論是我上面的示例代碼還是平常的web開發(fā)中,spirngboot默認(rèn)為單例模式,自定義的成員變量是線程不安全的,需要通過ThreadLocal 或這其他方法做同步處理。
回到我們當(dāng)前的業(yè)務(wù)場景,在這里我們需要每個線程處理的value值不同,互不影響,那么通過@Scope("prototype")
注解把TaskThread設(shè)置為多例模式。
總結(jié)
通過上面的示例,我們可以看到springboot與多線程的結(jié)合還是比較簡單,通過配置,我們既可以在spring容器中管理線程類,也可以在線程中使用sping容器中的對象實(shí)例。同時我們在使用的過程當(dāng)中要有意識的去注意線程安全方面的問題和內(nèi)部運(yùn)行機(jī)制的問題。當(dāng)然這里理解的還是比較淺顯,如果有不正確的地方還請大家指出與海涵。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。