spring boot中多線程開(kāi)發(fā)的注意事項(xiàng)總結(jié)
前言
Springt通過(guò)任務(wù)執(zhí)行器(TaskExecutor)來(lái)實(shí)現(xiàn)多線程和并發(fā)編程。使用ThreadPoolTaskExecutor可實(shí)現(xiàn)一個(gè)基于線程池的TaskExecutor。而實(shí)際開(kāi)發(fā)中任務(wù)一般是非阻礙的,即異步的,所以我們要在配置類(lèi)中通過(guò)@EnableAsync 開(kāi)啟對(duì)異步任務(wù)的支持,并通過(guò)實(shí)際執(zhí)行Bean的方法中使用@Async注解來(lái)聲明其是一個(gè)異步任務(wù)。
基于springboot的多線程程序開(kāi)發(fā)過(guò)程中,由于本身也需要注入spring容器進(jìn)行管理,才能發(fā)揮springboot的優(yōu)勢(shì)。所以這篇文字主要用來(lái)記錄開(kāi)發(fā)中兩者結(jié)合時(shí)需要注意的一些事項(xiàng)。
注意事項(xiàng)
第一步我們把線程類(lèi)的實(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);
//這里主要保存上下文對(duì)象實(shí)例,需要加上。SpringBootUtils類(lèi)網(wǎng)上很多,可以自己搜下
SpringBootUtils.setApplicationContext(app);
}
//access command line arguments
@Override
public void run(String... args) throws Exception {
//do something
}
}
//ComponentScan注解會(huì)掃描com.demo.thead下,也就是多線程類(lèi)所在的包下的文件
@Configuration
@ComponentScan(basePackages = { "com.demo.thread"})
public class ThreadConfig{
}
這里使用springboot @Import 注解,把ThreadConfig里掃描到的包中帶注解的示例,如@Component等注入到spring容器當(dāng)中.
然后是線程的啟動(dòng),這里在我的業(yè)務(wù)場(chǎng)景中有兩種情況:
1、程序運(yùn)行時(shí),自動(dòng)啟動(dòng);
這在一般的可執(zhí)行程序里面,當(dāng)然可以直接在main函數(shù)里執(zhí)行通過(guò)代碼啟動(dòng)線程。但在springboot中,我們可以使用@PostConstruct注解的方式,讓已經(jīng)注入bean容器的線程對(duì)象自啟動(dòng)
@Component
public class demoThread extends Thread
{
//注意這里,如果你沒(méi)有實(shí)現(xiàn)把多線程類(lèi)的實(shí)例注入到spring容器中,這里你是無(wú)法拿到其他自動(dòng)裝配的對(duì)象實(shí)例的的,這也是我們第一步的意義所在。
@Autowired
private XxxService xxxService;
@PostConstruct
public void start() {
super.start();
}
public void run() {
// Ok,在這里你就可以實(shí)現(xiàn)線程要實(shí)現(xiàn)的功能邏輯了,自然也可以直接使用裝配好的sevice對(duì)象實(shí)例。
}
}
2、在程序中,需要開(kāi)啟線程時(shí)啟動(dòng),比如在從kafka接收數(shù)據(jù),開(kāi)啟線程處理,當(dāng)然這種情況下也需要通過(guò)第一步,把線程類(lèi)實(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());
//通過(guò)SpringBootUtils獲取線程類(lèi)的實(shí)例
thread = SpringBootUtils.getBean(TaskThread.class);
//啟動(dòng)線程
//new Thread(thread).start() ;
//向線程對(duì)象里傳值
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 對(duì)象,單例模式下可以保證成員變量的線程安全和獨(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這個(gè)線程類(lèi)在spirngboot中是否要添加@Scope("prototype")注解設(shè)置為多例模式還是默認(rèn)單例模式。
在單例模式下SpringBootUtils.getBean(TaskThread.class) 每次返回的都是同一個(gè)對(duì)象,雖然不需要每次都創(chuàng)建新的對(duì)象,但無(wú)法保證成員變量的線程安全,也就是說(shuō)在線程池中的執(zhí)行的線程,它們的value值是共享的。而多例模式下,由于每次創(chuàng)建的都是一個(gè)新的線程對(duì)象,則不存在上述問(wèn)題。
所以在這里請(qǐng)大家注意無(wú)論是我上面的示例代碼還是平常的web開(kāi)發(fā)中,spirngboot默認(rèn)為單例模式,自定義的成員變量是線程不安全的,需要通過(guò)ThreadLocal 或這其他方法做同步處理。
回到我們當(dāng)前的業(yè)務(wù)場(chǎng)景,在這里我們需要每個(gè)線程處理的value值不同,互不影響,那么通過(guò)@Scope("prototype")注解把TaskThread設(shè)置為多例模式。
總結(jié)
通過(guò)上面的示例,我們可以看到springboot與多線程的結(jié)合還是比較簡(jiǎn)單,通過(guò)配置,我們既可以在spring容器中管理線程類(lèi),也可以在線程中使用sping容器中的對(duì)象實(shí)例。同時(shí)我們?cè)谑褂玫倪^(guò)程當(dāng)中要有意識(shí)的去注意線程安全方面的問(wèn)題和內(nèi)部運(yùn)行機(jī)制的問(wèn)題。當(dāng)然這里理解的還是比較淺顯,如果有不正確的地方還請(qǐng)大家指出與海涵。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
SpringCloud如何解決服務(wù)之間的通信問(wèn)題
本文主要介紹了SpringCloud如何解決服務(wù)之間的通信問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-08-08
SpringBoot多環(huán)境開(kāi)發(fā)該如何配置
這篇文章主要介紹了 SpringBoot多環(huán)境的開(kāi)發(fā)配置詳情,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09
java中基本數(shù)據(jù)類(lèi)型與Object的關(guān)系說(shuō)明
這篇文章主要介紹了java基本數(shù)據(jù)類(lèi)型與Object的關(guān)系說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java實(shí)現(xiàn)控制小數(shù)精度的方法
這篇文章主要介紹了Java實(shí)現(xiàn)控制小數(shù)精度的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
完美解決因數(shù)據(jù)庫(kù)一次查詢數(shù)據(jù)量過(guò)大導(dǎo)致的內(nèi)存溢出問(wèn)題
今天小編就為大家分享一篇完美解決因數(shù)據(jù)庫(kù)一次查詢數(shù)據(jù)量過(guò)大導(dǎo)致的內(nèi)存溢出問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Java漢字轉(zhuǎn)拼音工具類(lèi)完整代碼實(shí)例
這篇文章主要介紹了java漢字轉(zhuǎn)拼音工具類(lèi)完整代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

