多線程之線程池的應用

我們在開發的過程中經常會遇到多線程問題,多線程在整個java基礎也是計較重要的知識點。今天跟大家分享一個我在寫程序過程中遇到的線程池的簡單應用。

在Java1.5中提供了一個非常高效實用的多線程包:java.util.concurrent,提供了大量高級工具,可以幫助開發者編寫高效易維護、結構清晰的Java多線程程序。

線程池?

之前我們在使用多線程都是用Thread的start()來創建啓動一個線程,但是在實際開發中,如果每個請求到達就創建一個新線程,開銷是相當大的。服務器在創建和銷燬線程上花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用請求的時間和資源要多的多。除了創建和銷燬線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm裏創建太多的線程,可能會使系統由於過度消耗內存或“切換過度”而導致系統資源不足。這就引入了線程池概念。

線程池的原理其實就是對多線程的一個管理(可以抽象的理解爲原來是一個工廠來訂單了,老闆就聘請一個工人(線程)幹活,幹完活就把他炒魷魚了,這樣每次來訂單都得聘請一個工人,且老闆得不停去聘請工人,並分配任務,現在老闆就請了一個主管(線程池),並和工人簽訂長期合同,這樣每次有訂單(任務)來了,直接交給主管調度工人去幹活),爲了實現異步機制的一種方法,其實就是多個線程執行多個任務,最終這些線程通過線程池進行管理…不用手動去維護…一次可以處理多個任務,這樣就可以迅速的進行響應…

在java.util.concurrent包下,提供了一系列與線程池相關的類。合理的使用線程池,可以帶來多個好處

  • (1)降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗;
  • (2)提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行;
  • (3)提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
  • (4)線程池可以應對突然大爆發量的訪問,通過有限個固定線程爲大量的操作服務,減少創建和銷燬線程所需的時間。

如何使用?

1.創建線程池

2.創建任務

3.執行任務

4.關閉線程池

一、創建線程池的方法

我們一般通過工具類Executors的靜態方法來獲取線程池或靜態方法。

介紹四種常用創建方法

1. ExecutorService executorService1 = Executors.newSingleThreadExecutor();

單例線程,表示在任意的時間段內,線程池中只有一個線程在工作…

2. ExecutorService executorService2 = Executors.newFixedThreadPool(10);

固定線程池,表示創建一個可複用的固定線程池,線程池中最大線程個數爲10,當有任務被提交時,創建一個線程去執行,直到達到當線程池中最大個數爲止。當提交的任務個數大於線程池中最大線程個數時,則存入隊列中,等待執行。如果某個線程在執行期間發生了異常而終止,會補充一個新線程去執行餘下。

3. ExecutorService executorService3 = Executors.newScheduledThreadPool();

調度型線程池,一個大小無限的線程池,線程池會根據Scheduled(任務列表)進行延遲執行,或者是進行週期性的執行.適用於一些週期性的工作.支持定時以及週期性執行任務的需求任務。

4. ExecutorService executorService4 = Executors.newCacheThreadPool();

緩存線程池,無固定大小緩存線程池,先查看線程池中是否有當前執行線程的緩存,如果有就resue(複用),如果沒有,那麼需要創建一個線程來完成當前的調用.並且這類線程池只能完成一些生存期很短的一些任務.並且這類線程池內部規定能resue(複用)的線程,空閒的時間不能超過60s,一旦超過了60s,就會被移出線程池.非常靈活
例子

public class Executor {
    public static void main(String[] args) {
        //定義了線程池中最大存在的線程數目
        ExecutorService executorService=Executors.newFixedThreadPool(10);
        //添加了一個任務...
        executorService.execute(new Runnable() {

            @Override
            public void run() {
                while(true){
                    System.out.println("begincode");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

執行結果就是無限循環每隔1秒打印一個begincode…

二、創建任務

任務分爲兩種一種是有返回值的,一種是沒有返回值的

  • 無返回值的任務就是一個實現了runnable接口的類.使用run方法
  • 有返回值的任務是一個實現了callable接口的類.使用call方法
public class Begincode implements Runnable {
    public void run() {
        System.out.println(“Begincode--runable”);
    }  
} 

public class Begincode implements callable{
    public String call() {
        System.out.println(“Begincode--callable”);
        return "Begincode";
    }
} 

三、執行任務

通過java.util.concurrent.ExecutorService接口對象來執行任務,該對象有兩個方法可以執行任務execute和submit

  • execute這種方式提交沒有返回值,也就不能判斷是否執行成功。

  • submit這種方式它會返回一個Future對象,通過future的get方法來獲取返回值,get方法會阻塞住直到任務完成。

四、關閉線程池

當我們不需要使用線程池的時候,我們需要對其進行關閉…有兩種方法可以關閉掉線程池…

  • shutdown()

    • shutdown並不是直接關閉線程池,而是不再接受新的任務…如果線程池內有任務,那麼把這些任務執行完畢後,關閉線程池….
  • shutdownNow()

    • shutdownNow這個方法表示不再接受新的任務,並把任務隊列中的任務直接移出掉,如果有正在執行的,嘗試進行停止…

五、案例分享

場景:從數據庫中獲取url,並利用httpclient循環訪問url地址,並對返回結果進行操作
分析:由於是循環的對多個url進行訪問並獲取數據,爲了執行的效率,考慮使用多線程,url數量未知如果每個任務都創建一個線程將消耗大量的系統資源,最後決定使用線程池。

public class GetMonitorDataService {

    private Logger logger = LoggerFactory.getLogger(GetMonitorDataService.class);
    @Resource
    private MonitorProjectUrlMapper groupUrlMapper;
    @Resource
    private MonitorDetailBatchInsertMapper monitorDetailBatchInsertMapper;
    public void sendData(){
        //調用dao查詢所有url
        MonitorProjectUrlExample example=new MonitorProjectUrlExample();
        List<MonitorProjectUrl> list=groupUrlMapper.selectByExample(example);
        logger.info("此次查詢數據庫中監控url個數爲"+list.size());

        //獲取系統處理器個數,作爲線程池數量
        int nThreads=Runtime.getRuntime().availableProcessors();

        //定義一個裝載多線程返回值的集合
        List<MonitorDetail> result= Collections.synchronizedList(new ArrayList<MonitorDetail>());
        //創建線程池,這裏定義了一個創建線程池的工具類,避免了創建多個線程池
        ExecutorService executorService = ThreadPoolFactoryUtil.getExecutorService(nThreads);
        //遍歷數據庫取出的url
        if(list!=null&&list.size()>0) {
            for (MonitorProjectUrl monitorProjectUrl : list) {
                String url = monitorProjectUrl.getMonitorUrl();
                //創建任務
                ThreadTask threadTask = new ThreadTask(url, result);
                //執行任務
                executorService.execute(threadTask);
            }
            //對數據進行操作
            saveData(result);
        }
    }

任務:

public class ThreadTask implements Runnable{
    //這裏實現runnable接口
    private String url;
    private List<MonitorDetail> list;
    public ThreadTask(String url,List<MonitorDetail> list){
        this.url=url;
        this.list=list;
    }
    //把獲取的數據進行處理
    @Override
    public void run() {
        MonitorDetail detail = HttpClientUtil.send(url, MonitorDetail.class);
        list.add(detail);
    }

}

文中案例爲監控系統故沒有關閉線程池


以上是本人的一些分享,希望和大家多多交流。如果哪裏有不足,希望指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章