程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 關於java.util.concurrent您不知道的5件事,第2部分

關於java.util.concurrent您不知道的5件事,第2部分

編輯:關於JAVA

並發 Collections 提供了線程安全、經過良好調優的數據結構,簡化了並發編程。然而, 在一些情形下,開發人員需要更進一步,思考如何調節和/或限制線程執行。由於 java.util.concurrent 的總體目標是簡化多線程編程,您可能希望該包包含同步實用程序,而 它確實包含。

本文是 第 1 部分 的延續,將介紹幾個比核心語言原語(監視器)更高級的同步結構,但 它們還未包含在 Collection 類中。一旦您了解了這些鎖和門的用途,使用它們將非常直觀。

1. Semaphore

在一些企業系統中,開發人員經常需要限制未處理的特定資源請求(線程/操作)數量,事 實上,限制有時候能夠提高系統的吞吐量,因為它們減少了對特定資源的爭用。盡管完全可以 手動編寫限制代碼,但使用 Semaphore 類可以更輕松地完成此任務,它將幫您執行限制,如清 單 1 所示:

清單 1. 使用 Semaphore 執行限制

import java.util.*;import java.util.concurrent.*;

public class SemApp
{
   public static void main(String[] args)
   {
     Runnable limitedCall = new Runnable() {
       final Random rand = new Random();
       final Semaphore available = new Semaphore(3);
       int count = 0;
       public void run()
       {
         int time = rand.nextInt(15);
         int num = count++;

         try
         {
           available.acquire();

           System.out.println("Executing " +
             "long-running action for " +
             time + " seconds... #" + num);

           Thread.sleep(time * 1000);

           System.out.println("Done with #" +
             num + "!");

           available.release();
         }
         catch (InterruptedException intEx)
         {
           intEx.printStackTrace();
         }
       }
     };

     for (int i=0; i<10; i++)
       new Thread(limitedCall).start();
   }
}

即使本例中的 10 個線程都在運行(您可以對運行 SemApp 的 Java 進程執行 jstack 來驗 證),但只有 3 個線程是活躍的。在一個信號計數器釋放之前,其他 7 個線程都處於空閒狀 態。(實際上,Semaphore 類支持一次獲取和釋放多個 permit,但這不適用於本場景。)

2. CountDownLatch

如果 Semaphore 是允許一次進入一個(這可能會勾起一些流行夜總會的保安的記憶)線程 的並發性類,那麼 CountDownLatch 就像是賽馬場的起跑門柵。此類持有所有空閒線程,直到 滿足特定條件,這時它將會一次釋放所有這些線程。

清單 2. CountDownLatch:讓我們去賽馬吧!

import java.util.*;
import java.util.concurrent.*;

class Race
{
   private Random rand = new Random();

   private int distance = rand.nextInt(250);
   private CountDownLatch start;
   private CountDownLatch finish;

   private List<String> horses = new ArrayList<String> ();

   public Race(String... names)
   {
     this.horses.addAll(Arrays.asList(names));
   }

   public void run()
     throws InterruptedException
   {
     System.out.println("And the horses are stepping up to the  gate...");
     final CountDownLatch start = new CountDownLatch(1);
     final CountDownLatch finish = new CountDownLatch(horses.size ());
     final List<String> places =
       Collections.synchronizedList(new ArrayList<String> ());

     for (final String h : horses)
     {
       new Thread(new Runnable() {
         public void run() {
           try
           {
             System.out.println(h +
               " stepping up to the gate...");
             start.await();

             int traveled = 0;
             while (traveled < distance)
             {
               // In a 0-2 second period of time....
               Thread.sleep(rand.nextInt(3) * 1000);

               // ... a horse travels 0-14 lengths 
               traveled += rand.nextInt(15);
               System.out.println(h +
                 " advanced to " + traveled + "!");
             }
             finish.countDown();
             System.out.println(h +
               " crossed the finish!");
             places.add(h);
           }
           catch (InterruptedException intEx)
           {
             System.out.println("ABORTING RACE!!!");
             intEx.printStackTrace();
           }
         }
       }).start();
     }

     System.out.println("And... they're off!");
     start.countDown();

     finish.await();
     System.out.println("And we have our winners!");
     System.out.println(places.get(0) + " took the gold...");
     System.out.println(places.get(1) + " got the silver...");
     System.out.println("and " + places.get(2) + " took home the  bronze.");
   }
}

public class CDLApp
{
   public static void main(String[] args)
     throws InterruptedException, java.io.IOException
   {
     System.out.println("Prepping...");

     Race r = new Race(
       "Beverly Takes a Bath",
       "RockerHorse",
       "Phineas",
       "Ferb",
       "Tin Cup",
       "I'm Faster Than a Monkey",
       "Glue Factory Reject" 
       );

     System.out.println("It's a race of " + r.getDistance() + "  lengths");

     System.out.println("Press Enter to run the race....");
     System.in.read();

     r.run();
   }
}

注意,在 清單 2 中,CountDownLatch 有兩個用途:首先,它同時釋放所有線程,模擬馬 賽的起點,但隨後會設置一個門闩模擬馬賽的終點。這樣,“主” 線程就可以輸出結果。為了 讓馬賽有更多的輸出注釋,可以在賽場的 “轉彎處” 和 “半程” 點,比如賽馬跨過跑道的 四分之一、二分之一和四分之三線時,添加 CountDownLatch。

3. Executor

清單 1 和 清單 2 中的示例都存在一個重要的缺陷,它們要求您直接創建 Thread 對象。 這可以解決一些問題,因為在一些 JVM 中,創建 Thread 是一項重量型的操作,重用現有 Thread 比創建新線程要容易得多。而在另一些 JVM 中,情況正好相反:Thread 是輕量型的, 可以在需要時很容易地新建一個線程。當然,如果 Murphy 擁有自己的解決辦法(他通常都會 擁有),那麼您無論使用哪種方法對於您最終將部署的平台都是不對的。

JSR-166 專家組在一定程度上預測到了這一情形。Java 開發人員無需直接創建 Thread,他 們引入了 Executor 接口,這是對創建新線程的一種抽象。如清單 3 所示,Executor 使您不 必親自對 Thread 對象執行 new 就能夠創建新線程:

清單 3. Executor

Executor exec = getAnExecutorFromSomeplace();
exec.execute(new Runnable() { ... });

使用 Executor 的主要缺陷與我們在所有工廠中遇到的一樣:工廠必須來自某個位置。不幸 的是,與 CLR 不同,JVM 沒有附帶一個標准的 VM 級線程池。

Executor 類實際上 充當著一個提供 Executor 實現實例的共同位置,但它只有 new 方法 (例如用於創建新線程池);它沒有預先創建實例。所以您可以自行決定是否希望在代碼中創 建和使用 Executor 實例。(或者在某些情況下,您將能夠使用所選的容器/平台提供的實例。 )

ExecutorService 隨時可以使用

盡管不必擔心 Thread 來自何處,但 Executor 接口缺乏 Java 開發人員可能期望的某種功 能,比如結束一個用於生成結果的線程並以非阻塞方式等待結果可用。(這是桌面應用程序的 一個常見需求,用戶將執行需要訪問數據庫的 UI 操作,然後如果該操作花費了很長時間,可 能希望在它完成之前取消它。)

對於此問題,JSR-166 專家創建了一個更加有用的抽象(ExecutorService 接口),它將線 程啟動工廠建模為一個可集中控制的服務。例如,無需每執行一項任務就調用一次 execute() ,ExecutorService 可以接受一組任務並返回一個表示每項任務的未來結果的未來列表。

4. ScheduledExecutorServices

盡管 ExecutorService 接口非常有用,但某些任務仍需要以計劃方式執行,比如以確定的 時間間隔或在特定時間執行給定的任務。這就是 ScheduledExecutorService 的應用范圍,它 擴展了 ExecutorService。

如果您的目標是創建一個每隔 5 秒跳一次的 “心跳” 命令,使用 ScheduledExecutorService 可以輕松實現,如清單 4 所示:

清單 4. ScheduledExecutorService 模擬心跳

import java.util.concurrent.*;

public class Ping
{
   public static void main(String[] args)
   {
     ScheduledExecutorService ses =
       Executors.newScheduledThreadPool(1);
     Runnable pinger = new Runnable() {
       public void run() {
         System.out.println("PING!");
       }
     };
     ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
   }
}

這項功能怎麼樣?不用過於擔心線程,不用過於擔心用戶希望取消心跳時會發生什麼,也不 用明確地將線程標記為前台或後台;只需將所有的計劃細節留給 ScheduledExecutorService。

順便說一下,如果用戶希望取消心跳,scheduleAtFixedRate 調用將返回一個 ScheduledFuture 實例,它不僅封裝了結果(如果有),還擁有一個 cancel 方法來關閉計劃 的操作。

5. Timeout 方法

為阻塞操作設置一個具體的超時值(以避免死鎖)的能力是 java.util.concurrent 庫相比 起早期並發特性的一大進步,比如監控鎖定。

這些方法幾乎總是包含一個 int/TimeUnit 對,指示這些方法應該等待多長時間才釋放控制 權並將其返回給程序。它需要開發人員執行更多工作 — 如果沒有獲取鎖,您將如何重新獲取 ? — 但結果幾乎總是正確的:更少的死鎖和更加適合生產的代碼。

結束語

java.util.concurrent 包還包含了其他許多好用的實用程序,它們很好地擴展到了 Collections 之外,尤其是在 .locks 和 .atomic 包中。深入研究,您還將發現一些有用的控 制結構,比如 CyclicBarrier 等。

與 Java 平台的許多其他方面一樣,您無需費勁地查找可能非常有用的基礎架構代碼。在編 寫多線程代碼時,請記住本文討論的實用程序和 上一篇文章 中討論的實用程序。

下一次,我們將進入一個新的主題:關於 Jars 您不知道的 5 件事。

原文地址:http://www.ibm.com/developerworks/cn/java/j-5things5.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved