查看: 1601|回復: 0

[Java代碼] Java多線程之線程池的應用

3萬

主題

3萬

帖子

10萬

積分

管理員

Rank: 9Rank: 9Rank: 9

積分
100197
發表于 2017-2-21 19:53:52
在Java1.5中提供了一個非常高效實用的多線程包:java.util.concurrent,提供了大量高級工具,可以幫助開發者編寫高效易維護、結構清晰的Java多線程程序。
線程池
之前我們在使用多線程都是用Thread的start()來創建啟動一個線程,但是在實際開發中,如果每個請求到達就創建一個新線程,開銷是相當大的。服務器在創建和銷毀線程上花費的時間和消耗的系統資源都相當大,甚至可能要比在處理實際的用請求的時間和資源要多的多。除了創建和銷毀線程的開銷之外,活動的線程也需要消耗系統資源。如果在一個jvm里創建太多的線程,可能會使系統由于過度消耗內存或“切換過度”而導致系統資源不足。這就引入了線程池概念。
線程池的原理其實就是對多線程的一個管理,為了實現異步機制的一種方法,其實就是多個線程執行多個任務,最終這些線程通過線程池進行管理…不用手動去維護…一次可以處理多個任務,這樣就可以迅速的進行相應…比如說一個網站成為了熱點網站,那么對于大量的點擊量,就必須要對每一次的點擊做出迅速的處理,這樣才能達到更好的交互效果…這樣就需要多個線程去處理這些請求,以便能夠更好的提供服務…
在java.util.concurrent包下,提供了一系列與線程池相關的類。合理的使用線程池,可以帶來多個好處:
(1)降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗;
(2)提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行;
(3)提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程池可以應對突然大爆發量的訪問,通過有限個固定線程為大量的操作服務,減少創建和銷毀線程所需的時間。
使用線程池:
1.創建線程池
2.創建任務
3.執行任務
4.關閉線程池
一、創建線程池的方法
我們一般通過工具類Executors的靜態方法來獲取線程池或靜態方法。介紹四種常用創建方法
  1. ExecutorService executorService1 = Executors.newSingleThreadExecutor();
復制代碼
單例線程,表示在任意的時間段內,線程池中只有一個線程在工作…
  1. ExecutorService executorService2 = Executors.newFixedThreadPool(10);
復制代碼


緩存線程池,先查看線程池中是否有當前執行線程的緩存,如果有就resue(復用),如果沒有,那么需要創建一個線程來完成當前的調用.并且這類線程池只能完成一些生存期很短的一些任務.并且這類線程池內部規定能resue(復用)的線程,空閑的時間不能超過60s,一旦超過了60s,就會被移出線程池.
  1. ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
復制代碼

固定型線程池,和newCacheThreadPool()差不多,也能夠實現resue(復用),但是這個池子規定了線程的最大數量,也就是說當池子有空閑時,那么新的任務將會在空閑線程中被執行,一旦線程池內的線程都在進行工作,那么新的任務就必須等待線程池有空閑的時候才能夠進入線程池,其他的任務繼續排隊等待.這類池子沒有規定其空閑的時間到底有多長.這一類的池子更適用于服務器.
  1. ExecutorService executorService4 = Executors.newCacheThreadPool();
復制代碼
調度型線程池,調度型線程池會根據Scheduled(任務列表)進行延遲執行,或者是進行周期性的執行.適用于一些周期性的工作.
先看一個簡單例子:
  1. public class Executor {
  2.     public static void main(String[] args) {
  3.         //定義了線程池中最大存在的線程數目
  4.         ExecutorService executorService=Executors.newFixedThreadPool(10);
  5.         //添加了一個任務...
  6.         executorService.execute(new Runnable() {

  7.             @Override
  8.             public void run() {
  9.                 while(true){
  10.                     System.out.println("begincode");
  11.                     try {
  12.                         Thread.sleep(1000);
  13.                     } catch (InterruptedException e) {
  14.                         e.printStackTrace();
  15.                     }
  16.                 }
  17.             }
  18.         });
  19.     }
  20. }
復制代碼
執行結果就是無限循環每隔1秒打印一個begincode…
二、創建任務
任務分為兩種一種是有返回值的,一種是沒有返回值的
無返回值的任務就是一個實現了runnable接口的類.使用run方法
有返回值的任務是一個實現了callable接口的類.使用call方法
例子:
  1. public class Begincode implements Runnable {   
  2.     public void run() {  
  3.         System.out.println(“Begincode--runable”);  
  4.     }  
  5. }
復制代碼
  1. public class Begincode implements callable{   
  2.     public void call() {  
  3.         System.out.println(“Begincode--callable”);  
  4.     }  
  5. }
復制代碼
三、執行任務
通過java.util.concurrent.ExecutorService接口對象來執行任務,該對象有兩個方法可以執行任務execute和submit
execute這種方式提交沒有返回值,也就不能判斷是否執行成功。
submit這種方式它會返回一個Future對象,通過future的get方法來獲取返回值,get方法會阻塞住直到任務完成。
四、關閉線程池
當我們不需要使用線程池的時候,我們需要對其進行關閉…有兩種方法可以關閉掉線程池…
shutdown()
shutdown并不是直接關閉線程池,而是不再接受新的任務…如果線程池內有任務,那么把這些任務執行完畢后,關閉線程池….
shutdownNow()
這個方法表示不再接受新的任務,并把任務隊列中的任務直接移出掉,如果有正在執行的,嘗試進行停止…
案例分享
需求:從數據庫中獲取url,并利用httpclient循環訪問url地址,并對返回結果進行操作
分析:由于是循環的對多個url進行訪問并獲取數據,為了執行的效率,考慮使用多線程,url數量未知如果每個任務都創建一個線程將消耗大量的系統資源,最后決定使用線程池。
給大家貼上代碼
  1. public class GetMonitorDataService {

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

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

  14.         //定義一個裝載多線程返回值的集合
  15.         List<MonitorDetail> result= Collections.synchronizedList(new ArrayList<MonitorDetail>());
  16.         //創建線程池,這里定義了一個創建線程池的工具類,避免了創建多個線程池
  17.         ExecutorService executorService = ThreadPoolFactoryUtil.getExecutorService(nThreads);
  18.         //遍歷數據庫取出的url
  19.         if(list!=null&&list.size()>0) {
  20.             for (MonitorProjectUrl monitorProjectUrl : list) {
  21.                 String url = monitorProjectUrl.getMonitorUrl();
  22.                 //創建任務
  23.                 ThreadTask threadTask = new ThreadTask(url, result);
  24.                 //執行任務
  25.                 executorService.execute(threadTask);
  26.             }
  27.             //對數據進行操作
  28.             saveData(result);
  29.         }
  30.     }
復制代碼
任務:

  1. public class ThreadTask implements Runnable{
  2.     //這里實現runnable接口
  3.     private String url;
  4.     private List<MonitorDetail> list;
  5.     public ThreadTask(String url,List<MonitorDetail> list){
  6.         this.url=url;
  7.         this.list=list;
  8.     }
  9.     //把獲取的數據進行處理
  10.     @Override
  11.     public void run() {
  12.         MonitorDetail detail = HttpClientUtil.send(url, MonitorDetail.class);
  13.         list.add(detail);
  14.     }

  15. }
復制代碼




回復

使用道具 舉報