posts - 167,  comments - 30,  trackbacks - 0

          jdk并發部分,如果英文理解有點小難,可以參考http://www.yiibai.com/java6/java/util/concurrent/package-summary.html
          本篇轉自:http://victorzhzh.iteye.com/blog/1011635

          很多時候我們希望任務可以定時的周期性的執行,在最初的JAVA工具類庫中,通過Timer可以實現定時的周期性的需求,但是有一定的缺陷,例如:Timer是基于絕對時間的而非支持相對時間,因此Timer對系統時鐘比較敏感。雖然有一定的問題,但是我們還是從這個最簡單的實現開始研究。

           

          首先,我們準備一些討論問題的類:TimerTask1和TimerLongTask,如下

          Java代碼  收藏代碼
          1. public class TimerTask1 extends TimerTask {  
          2.   
          3.     @Override  
          4.     public void run() {  
          5.         String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
          6.         Random random = new Random();  
          7.         StringBuffer sb = new StringBuffer();  
          8.         for (int i = 0; i < 10; i++) {  
          9.             int number = random.nextInt(base.length());  
          10.             sb.append(base.charAt(number));  
          11.         }  
          12.         System.out.println(new Date()+","+sb.toString());  
          13.     }  
          14.   
          15. }  

           這個類負責生成一個含有10個字符的字符串,這里我們將輸出時間打印出來,近似認為是任務執行的時間。

          Java代碼  收藏代碼
          1. public class TimerLongTask extends TimerTask {  
          2.   
          3.     @Override  
          4.     public void run() {  
          5.         System.out.println("TimerLongTask: 開始沉睡");  
          6.         try {  
          7.             TimeUnit.SECONDS.sleep(10);  
          8.         } catch (InterruptedException e) {  
          9.             e.printStackTrace();  
          10.         }  
          11.         System.out.println("TimerLongTask: 已經醒來");  
          12.     }  
          13.   
          14. }  

           這個類啟動了一個長任務,即讓任務沉睡10秒。

           

          下面我們來看一個定時任務執行的例子:

          Java代碼  收藏代碼
          1. public static void main(String[] args) throws InterruptedException {  
          2.     Timer timer = new Timer();  
          3.     timer.schedule(new TimerTask1(), 1);  
          4.     timer.schedule(new TimerLongTask(), 1);  
          5.     timer.schedule(new TimerTask1(), 2);  
          6.     TimeUnit.SECONDS.sleep(20);  
          7.     timer.cancel();  
          8. }  

           在這個例子中,我們先提交了一個TimerTask1任務,且讓它延遲1毫秒執行,緊接著我們又提交了一個TimerLongTask長任務,且讓它也延遲1毫秒執行,最后我們在提交一個TimerTask1任務,延遲2毫秒執行。然后讓主線程沉睡20秒后關閉timer。我們看一下執行結果:

          Java代碼  收藏代碼
          1. Thu Apr 21 11:04:31 CST 2011,utg3hn7u4r  
          2. TimerLongTask: 開始沉睡  
          3. TimerLongTask: 已經醒來  
          4. Thu Apr 21 11:04:41 CST 2011,4aac22sud1  

           這里我們看到第一次輸出10個字符的時間和第二次輸出10個字符的時間上相差了10秒,這10秒恰恰是長任務沉睡的時間,通過這個輸出我們可以分析出:Timer用來執行任務的線程其實只有一個,且逐一被執行。接下來我們查看一下源碼驗證一下,如下:

          Java代碼  收藏代碼
          1. private TaskQueue queue = new TaskQueue();  
          2. private TimerThread thread = new TimerThread(queue);  

           這兩行代碼來自Timer源碼,我們可以看到在第一次創建了Timer時就已經創建了一個thread和一個queue,因此只有一個線程來執行我們的任務。

          那么Timer是如何來執行任務的?

          首先我們調用timer.schedule方法,將任務提交到timer中,Timer中有很多重載的schedule方法,但它們都會調用同一個方法即sched方法。這個方法會將我們提交的任務添加到TaskQueue的隊列中(即queue),在每次添加時都會根據nextExecutionTime大小來調整隊列中任務的順序,讓nextExecutionTime最小的排在隊列的最前端,nextExecutionTime最大的排在隊列的最后端。在創建Timer時,我們同時也創建了一個TimerThread即thread,并且啟動了這個線程,

          Java代碼  收藏代碼
          1. public Timer(String name) {  
          2.         thread.setName(name);  
          3.         thread.start();  
          4. }  

           TimerThread中的mainLoop方法是核心,它會完成所有的任務執行,在一開始我們的隊列為空,這時mainLoop方法將會使線程進入等待狀態,當我們使用schedule提交任務時會notify這個TimerThread線程,若任務的執行未到則在wait相對的時間差。

          我們調整一下上面的代碼,

          Java代碼  收藏代碼
          1. Timer timer = new Timer();  
          2. timer.schedule(new TimerTask1(), 1);  
          3. timer.schedule(new TimerTask1(), 5000);  
          4. timer.schedule(new TimerLongTask(), 3000);  
          5. TimeUnit.SECONDS.sleep(20);  
          6. timer.cancel();  

           這樣先提交兩個輸出字符的任務最后提交長任務,在這里,我們讓第二個輸出字符的任務延遲5秒執行,長任務延遲3秒執行,這樣得到的結果如下:

          Java代碼  收藏代碼
          1. Thu Apr 21 13:07:44 CST 2011,2sstwluvgc  
          2. TimerLongTask: 開始沉睡  
          3. TimerLongTask: 已經醒來  
          4. Thu Apr 21 13:07:57 CST 2011,sh4fnkqqc8  

           雖然我們改變了提交順序,但是還是按照延遲時間遞增排序執行的,兩個輸出字符串的時間之間相差13秒,這也是長任務等待執行時間+長任務睡眠時間之和。

           

          重復執行scheduleAtFixedRate方法提交任務,主要是調用rescheduleMin方法對已經調用的任務進行重新設置調度延遲,并調用fixDown方法對隊列里的任務根據延遲時間重新排序。

          Java代碼  收藏代碼
          1. Timer timer = new Timer();  
          2. timer.scheduleAtFixedRate(new TimerTask1(), 30005000);  

           3000,代表第一次執行時等待的時間,5000代表每次執行任務之間的時間間隔,運行結果:

          Java代碼  收藏代碼
          1. Thu Apr 21 13:14:55 CST 2011,izf536esrg  
          2. Thu Apr 21 13:15:00 CST 2011,2khzm7e09v  
          3. Thu Apr 21 13:15:05 CST 2011,jc3dvt2m8q  

           基本是每5秒運行一次。

           

          由于Timer只使用一個線程運行所有的任務,那么當一個任務拋出運行時異常后會有什么樣的情形呢?其他的任務是否可以繼續?我們已經有了前面的知識可以先猜想一個結果:因為Timer只使用一個線程運行所有的任務,所以當一個線程拋出運行時異常時,這個線程就基本掛了,不會在執行后續的任何代碼,因此我們可以斷言,當一個任務拋出運行時異常時,后續任務都不可以執行。為了證明這個猜想,我們需要一個可以拋出異常的任務,如下:

          Java代碼  收藏代碼
          1. public class TimerExceptionTask extends TimerTask {  
          2.   
          3.     @Override  
          4.     public void run() {  
          5.         System.out.println("TimerExceptionTask: "+new Date());  
          6.         throw new RuntimeException();  
          7.     }  
          8.   
          9. }  

           這個任務拋出一個運行時異常。接著我們需要定義一下我們任務執行的順序:先執行一個正常的任務,然后在執行一個拋出異常的任務,最后在執行一個正常的任務,如下:

          Java代碼  收藏代碼
          1. Timer timer = new Timer();  
          2. timer.schedule(new TimerTask1(), 1000);  
          3. timer.schedule(new TimerExceptionTask(), 3000);  
          4. timer.schedule(new TimerTask1(), 5000);  
          5. TimeUnit.SECONDS.sleep(6);  
          6. timer.cancel();  

           延遲1秒執行正常輸出字符串的任務,延遲3秒執行拋出異常的任務,延遲5秒執行正常輸出字符串的任務,看一下結果:

          Java代碼  收藏代碼
          1. Thu Apr 21 13:40:23 CST 2011,lk7fjneyyu  
          2. TimerExceptionTask: Thu Apr 21 13:40:25 CST 2011  
          3. Exception in thread "Timer-0" java.lang.RuntimeException  
          4.     at org.victorzhzh.concurrency.TimerExceptionTask.run(TimerExceptionTask.java:11)  
          5.     at java.util.TimerThread.mainLoop(Timer.java:512)  
          6.     at java.util.TimerThread.run(Timer.java:462)  

           并沒有輸出兩個字符串,只執行了第一次的輸出字符串任務,說明當拋出運行時異常時,其后續任務不可能被執行。

          鑒于Timer的缺陷,所以對它的使用還是要謹慎的,還好并發包中為我們提供了相應的替代品:


          我們將圍繞這些不足之處看看ScheduledThreadPoolExecutor是如何優化的。

          為了研究方便我們需要兩個類:

          Java代碼  收藏代碼
          1. public class Task1 implements Callable<String> {  
          2.   
          3.     @Override  
          4.     public String call() throws Exception {  
          5.         String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
          6.         Random random = new Random();  
          7.         StringBuffer sb = new StringBuffer();  
          8.         for (int i = 0; i < 10; i++) {  
          9.             int number = random.nextInt(base.length());  
          10.             sb.append(base.charAt(number));  
          11.         }  
          12.         System.out.println("Task1 running: " + new Date());  
          13.         return sb.toString();  
          14.     }  
          15.   
          16. }  

           生成含有10個字符的字符串,使用Callable接口目的是我們不再任務中直接輸出結果,而主動取獲取任務的結果

          Java代碼  收藏代碼
          1. public class LongTask implements Callable<String> {  
          2.   
          3.     @Override  
          4.     public String call() throws Exception {  
          5.         System.out.println("LongTask running: "+new Date());  
          6.         TimeUnit.SECONDS.sleep(10);  
          7.         return "success";  
          8.     }  
          9.   
          10. }  

          長任務類,這里我們讓任務沉睡10秒然后返回一個“success”

          下面我們來分析一下ScheduledThreadPoolExecutor:

          1、Timer中單線程問題是否在ScheduledThreadPoolExecutor中存在?

          我們先來看一下面的程序:

          Java代碼  收藏代碼
          1. public class ScheduledThreadPoolExec {  
          2.     public static void main(String[] args) throws InterruptedException,  
          3.             ExecutionException {  
          4.         <strong><span style="color: #ff0000;">ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(  
          5.                 2);</span>  
          6. </strong>  
          7.   
          8.   
          9.         ScheduledFuture future1 = executor.schedule(new Task1(), 5,  
          10.                 TimeUnit.SECONDS);  
          11.         ScheduledFuture future2 = executor.schedule(new LongTask(), 3,  
          12.                 TimeUnit.SECONDS);  
          13.   
          14.         BlockingQueue<ScheduledFuture> blockingQueue = new ArrayBlockingQueue<ScheduledFuture>(  
          15.                 2true);  
          16.         blockingQueue.add(future2);  
          17.         blockingQueue.add(future1);  
          18.   
          19.         System.out.println(new Date());  
          20.         while (!blockingQueue.isEmpty()) {  
          21.             ScheduledFuture future = blockingQueue.poll();  
          22.             if (!future.isDone())  
          23.                 blockingQueue.add(future);  
          24.             else  
          25.                 System.out.println(future.get());  
          26.         }  
          27.         System.out.println(new Date());  
          28.         executor.shutdown();  
          29.     }  
          30.   
          31. }  

           首先,我們定義了一個ScheduledThreadPoolExecutor它的池長度是2。接著提交了兩個任務:第一個任務將延遲5秒執行,第二個任務將延遲3秒執行。我們建立了一個BlockingQueue,用它來存儲了ScheduledFuture,使用ScheduledFuture可以獲得任務的執行結果。在一個while循環中,我們每次將一個ScheduledFuture從隊列中彈出,驗證它是否被執行,如果沒有被執行則再次將它加入隊列中,如果被執行了,這使用ScheduledFuture的get方法獲取任務執行的結果。看一下執行結果:

          Java代碼  收藏代碼
          1. Thu Apr 21 19:23:02 CST 2011  
          2. LongTask running: Thu Apr 21 19:23:05 CST 2011  
          3. Task1 running: Thu Apr 21 19:23:07 CST 2011  
          4. h1o2wd942e  
          5. success  
          6. Thu Apr 21 19:23:15 CST 2011  

           我們看到長任務先運行,因為長任務只等待了3秒,然后是輸出字符串的任務運行,兩個任務開始時間相差2秒,而先輸出了字符串而后才是長任務運行的結果success,最后我們查看一下整體的開始和結束時間相差了13秒。

          這說明在ScheduledThreadPoolExecutor中它不是以一個線程運行任務的,而是以多個線程,如果用一個線程運行任務,那么長任務運行完之前是不會運行輸出字符串任務的。其實這個“多個任務“是我們自己指定的注意一下標紅的代碼,如果我們把這行代碼改為:

          Java代碼  收藏代碼
          1. ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);  

           那么運行結果就會發生變化,

          Java代碼  收藏代碼
          1. Thu Apr 21 19:36:56 CST 2011  
          2. LongTask running: Thu Apr 21 19:36:59 CST 2011  
          3. success  
          4. Task1 running: Thu Apr 21 19:37:09 CST 2011  
          5. y981iqd0or  
          6. Thu Apr 21 19:37:09 CST 2011  

           這時其實和使用Timer運行結果是一樣的。任務是在一個線程里順序執行的。

           

          2、Timer中一但有運行時異常報出后續任務是否還會正常運行?

          為了研究這個問題,我們還是需要一個能夠拋出異常的任務,如下:

          Java代碼  收藏代碼
          1. public class TimerExceptionTask extends TimerTask {  
          2.   
          3.     @Override  
          4.     public void run() {  
          5.         System.out.println("TimerExceptionTask: "+new Date());  
          6.         throw new RuntimeException();  
          7.     }  
          8.   
          9. }  

           我們對上面運行任務的代碼做一點點小小的修改,先運行兩個拋出異常的任務,如下:

          Java代碼  收藏代碼
          1. public class ScheduledThreadPoolExec {  
          2.     public static void main(String[] args) throws InterruptedException,  
          3.             ExecutionException {  
          4.         ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(  
          5.                 2);  
          6.         ScheduledFuture future1 = executor.schedule(new Task1(), 5,  
          7.                 TimeUnit.SECONDS);  
          8.         ScheduledFuture future2 = executor.schedule(new LongTask(), 3,  
          9.                 TimeUnit.SECONDS);  
          10.         <strong><span style="color: #ff0000;">executor.schedule(new TimerExceptionTask(), 1, TimeUnit.SECONDS);  
          11.         executor.schedule(new TimerExceptionTask(), 2, TimeUnit.SECONDS);</span>  
          12. </strong>  
          13.   
          14.   
          15.   
          16.         BlockingQueue<ScheduledFuture> blockingQueue = new ArrayBlockingQueue<ScheduledFuture>(  
          17.                 2true);  
          18.         blockingQueue.add(future2);  
          19.         blockingQueue.add(future1);  
          20.   
          21.         System.out.println(new Date());  
          22.         while (!blockingQueue.isEmpty()) {  
          23.             ScheduledFuture future = blockingQueue.poll();  
          24.             if (!future.isDone())  
          25.                 blockingQueue.add(future);  
          26.             else  
          27.                 System.out.println(future.get());  
          28.         }  
          29.         System.out.println(new Date());  
          30.         executor.shutdown();  
          31.     }  
          32.   
          33. }  

           注意,標紅的代碼,如果這兩個代碼拋出錯誤后會影響后續任務,那么就應該在此終止,但是看一下結果,

          Java代碼  收藏代碼
          1. Thu Apr 21 19:40:15 CST 2011  
          2. TimerExceptionTask: Thu Apr 21 19:40:16 CST 2011  
          3. TimerExceptionTask: Thu Apr 21 19:40:17 CST 2011  
          4. LongTask running: Thu Apr 21 19:40:18 CST 2011  
          5. Task1 running: Thu Apr 21 19:40:20 CST 2011  
          6. v5gcf01iiz  
          7. success  
          8. Thu Apr 21 19:40:28 CST 2011  

           后續任務仍然執行,可能會有朋友說:“你上面的池設置的是2,所以很有可能是那兩個拋出異常的任務都在同一個線程中執行,而另一個線程執行了后續的任務”。那我們就把ScheduledThreadPoolExecutor設置成1看看,結果如下:

          Java代碼  收藏代碼
          1. Thu Apr 21 19:43:00 CST 2011  
          2. TimerExceptionTask: Thu Apr 21 19:43:01 CST 2011  
          3. TimerExceptionTask: Thu Apr 21 19:43:02 CST 2011  
          4. LongTask running: Thu Apr 21 19:43:03 CST 2011  
          5. success  
          6. Task1 running: Thu Apr 21 19:43:13 CST 2011  
          7. 33kgv8onnd  
          8. Thu Apr 21 19:43:13 CST 2011  

          后續任務也執行了,所以說ScheduledThreadPoolExecutor不會像Timer那樣有線程泄漏現象。

          對于周期性執行和Timer很類似這里就不再舉例了。

           

          posted on 2013-03-18 18:58 David1228 閱讀(1772) 評論(0)  編輯  收藏 所屬分類: JAVA線程-模式-設計

          <2013年3月>
          242526272812
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(4)

          隨筆分類

          隨筆檔案

          文章檔案

          新聞分類

          新聞檔案

          相冊

          收藏夾

          Java

          Linux知識相關

          Spring相關

          云計算/Linux/虛擬化技術/

          友情博客

          多線程并發編程

          開源技術

          持久層技術相關

          搜索

          •  

          積分與排名

          • 積分 - 358877
          • 排名 - 154

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 大埔县| 静海县| 武陟县| 益阳市| 禄丰县| 大竹县| 兴城市| 常熟市| 洪湖市| 泰和县| 临城县| 河池市| 武宣县| 镇赉县| 萨迦县| 海门市| 黔江区| 敦化市| 松桃| 文成县| 察隅县| 齐齐哈尔市| 桃园市| 泰宁县| 夏津县| 沙河市| 东山县| 息烽县| 汉阴县| 阿瓦提县| 桦川县| 郎溪县| 沅江市| 临安市| 常山县| 莫力| 临洮县| 麻江县| 鹤峰县| 陈巴尔虎旗| 桂平市|