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容器中的對象實例。同時我們在使用的過程當中要有意識的去注意線程安全方面的問題和內部運行機制的問題。當然這里理解的還是比較淺顯,如果有不正確的地方還請大家指出與海涵。

 

關注微信公眾號,查看更多技術文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM