1. 程式人生 > >springboot下多執行緒開發注意事項

springboot下多執行緒開發注意事項

基於springboot的多執行緒程式開發過程中,由於本身也需要注入spring容器進行管理,才能發揮springboot的優勢。所以這篇文字主要用來記錄開發中兩者結合時需要注意的一些事項。

第一步我們把執行緒類的例項注入sping容器進行管理

@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); //這裡主要儲存上下文物件例項,需要加上。SpringBootUtils類網上很多,可以自己搜下 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容器當中.

然後是執行緒的啟動,這裡在我的業務場景中有兩種情況:

1、程式執行時,自動啟動;

這在一般的可執行程式裡面,當然可以直接在main函式裡執行通過程式碼啟動執行緒。但在springboot中,我們可以使用@PostConstruct註解的方式,讓已經注入bean容器的執行緒物件自啟動

@Component
public class  demoThread extends Thread
{
    //注意這裡,如果你沒有實現把多執行緒類的例項注入到spring容器中,這裡你是無法拿到其他自動裝配的物件例項的的,這也是我們第一步的意義所在。
    @Autowired
    private XxxService xxxService;

    @PostConstruct
    public void start() {
        super.start();
    }

    public void run() {
        // Ok,在這裡你就可以實現執行緒要實現的功能邏輯了,自然也可以直接使用裝配好的sevice物件例項。
        
    }
}

 2、在程式中,需要開啟執行緒時啟動,比如在從kafka接收資料,開啟執行緒處理,當然這種情況下也需要通過第一步,把執行緒類例項注入到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獲取執行緒類的例項
           thread = SpringBootUtils.getBean(TaskThread.class);
           //啟動執行緒
           //new Thread(thread).start() ; 
           //向執行緒物件裡傳值
           thread.init(i);
           //放入執行緒池執行
           taskPool.execute(thread);

    }
//注意這裡是否新增@Scope("prototype")註解
@Component
@Scope("prototype")
public class TaskThread  implements Runnable{
    
    protected int value=0;

    @Autowired
    private XxxService xxxService;
    
    //ThreadLocal  物件,單例模式下可以保證成員變數的執行緒安全和獨立性。
    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")註解設定為多例模式還是預設單例模式。

在單例模式下SpringBootUtils.getBean(TaskThread.class) 每次返回的都是同一個物件,雖然不需要每次都建立新的物件,但無法保證成員變數的執行緒安全,也就是說線上程池中的多個執行緒物件,它們的value值是共享的。而多例模式下,由於每次建立的都是一個新的執行緒物件,則不存在上述問題。

所以在這裡請大家注意無論是我上面的示例程式碼還是平常的web開發中,spirngboot預設為單例模式,自定義的成員變數是執行緒不安全的,需要通過ThreadLocal 或這其他方法做同步處理。

那麼回到我們當前的業務場景,在這裡我們需要每個執行緒處理的value值不同,互不影響,那麼通過@Scope("prototype")註解把TaskThread設定為多例模式。

總結

通過上面的示例,我們可以看到springboot與多執行緒的結合還是比較簡單,通過配置,我們既可以在spring容器中管理執行緒類,也可以線上程中使用sping容器中的物件例項。同時我們在使用的過程當中要有意識的去注意執行緒安全方面的問題和內部執行機制的問題。當然這裡理解的還是比較淺顯,如果有不正確的地方還請大家指出與海涵。