排程規則

工作排程規則可用來控制您的工作關聯於其他工作的執行時機。尤其是,在並行作業可能造成結果不一致的情況中,排程規則可讓您防止多個工作並行執行。它們也可讓您確保一系列工作的執行次序。排程規則的作用,最好是用範例來展示。首先,我們要定義兩個用來同時開啟和關閉電燈開關的工作。

      public class LightSwitch {
      private boolean isOn = false;
      public boolean isOn() {
         return isOn;
      }
      public void on() {
         new LightOn().schedule();
      }
      public void off() {
         new LightOff().schedule();
      }
      class LightOn extends Job {
         public LightOn() {
            super("Turning on the light");
         }
      public IStatus run(IProgressMonitor monitor) {
            System.out.println("Turning the light on");
            isOn = true;
         return Status.OK_STATUS;
         }
      }
      class LightOff extends Job {
         public LightOff() {
            super("Turning off the light");
         }
      public IStatus run(IProgressMonitor monitor) {
            System.out.println("Turning the light off");
            isOn = false;
         return Status.OK_STATUS;
         }
      }
   }

現在,我們建立一個簡單程式,用來建立電燈開關,然後再開啟和關閉這個開關:

      LightSwitch light = new LightSwitch();
   light.on();
   light.off();
   System.out.println("The light is on? " + switch.isOn());

如果我們執行這個小程式夠多次,最後會得到下列輸出:

      Turning the light off
   Turning the light on
   The light is on? true

怎麼會這樣? 我們告訴電燈先開啟再關閉,因此,最終狀態應該是關閉才對呀! 問題是沒有東西可用來防止在執行 LightOn 工作的同時執行 LightOff 工作。因此,即使排定先執行 "on" 工作,它們並行執行也仍意謂著無法預測這兩個並行工作的確實執行次序。如果 LightOff 工作在 LightOn 工作之前結束執行,我們就會得到這個無效的結果。我們需要一種方式來防止這兩個工作同時執行,排程規則就在這裡開始介入。

我們可以建立一個簡單的排程規則作為互斥旗標(也稱為二進位號誌)來修正這個範例:

      class Mutex implements ISchedulingRule {
      public boolean isConflicting(ISchedulingRule rule) {
         return rule == this;
      }
      public boolean contains(ISchedulingRule rule) {
         return rule == this;
      }
   }

之後,這個規則便加到上一個範例的兩個電燈開關工作中:

      public class LightSwitch {
      final MutextRule rule = new MutexRule();
      ...
      class LightOn extends Job {
         public LightOn() {
            super("Turning on the light");
            setRule(rule);
         }
         ...
      }
      class LightOff extends Job {
         public LightOff() {
            super("Turning off the light");
            setRule(rule);
         }
         ...
      }
   }

現在,在排定兩個電燈開關工作之後,工作基礎架構會呼叫 isConflicting 方法來比較兩個工作的排程規則。它會發現這兩個工作的排程規則衝突,且會確定它們將依正確的方式來執行。它也會確定它們絕不會同時執行。現在,如果您執行這個範例程式一百萬次,永遠會得到相同的結果:

      Turning the light on
   Turning the light off
   The light is on? false

規則也可以作為一般的鎖定機制,獨立在工作之外。下列在 try/finally 區塊內取得一個規則,使其他執行緒和工作無法在 beginRuleendRule 呼叫之間執行這個規則。

      IJobManager manager = Platform.getJobManager();
try {      manager.beginRule(rule, monitor);
      ... do some work ...
   } finally {
      manager.endRule(rule);
   }

當利用這類程式撰寫型樣來取得和釋出排程規則時,您應該特別小心。如果您沒有結束您呼叫了 beginRule 的規則,您將永遠鎖定這個規則。

建立您自己的規則

雖然工作 API 定義了排程規則合同,但它實際上不提供任何排程規則的實作。基本上,一般基礎架構沒有任何方法可以得知哪幾組工作並行地執行不會有問題。依預設,工作並沒有排程規則,而且只要能夠建立執行排定工作的執行緒,就會儘快執行它。

當工作有排程規則時,會利用 isConflicting 方法來判斷這個規則是否與目前在執行中的任何工作之規則衝突。因此,您的 isConflicting 實作可以確實定義能夠安全執行您的工作的時機。在我們的電燈開關範例中,isConflicting 實作只是以所提供的規則來使用身分的比較。如果另一個工作也有相同的規則,它們不會並行地執行。當撰寫您自己的排程規則,請務必小心閱讀和遵循 isConflicting 的 API 合同。

如果您的工作有幾個互不相關的限制,您可以利用 MultiRule,將多個排程規則組合起來。比方說,如果您的工作需要開啟電燈開關,且會將資訊寫入網路 Socket 中, 它可以利用 Factory 方法 MultiRule.combine,將電燈開關的規則和 Socket 寫入權的規則結合在單一規則中。

規則階層

我們已討論過 ISchedulingRuleisConflicting 方法,但至今仍未提及 contains 方法。這個方法用於許多用戶端都不會需要的非常特殊的排程規則之運用。排程規則可以在邏輯上組成若干階層來進行對自然階層式資源的存取控制。這個概念最簡單的範例就是樹狀結構型檔案系統。如果應用程式想要取得某個目錄的專用鎖定,通常也隱含它想要獨佔性存取這個目錄內的檔案和子目錄。contains 方法便用來指定各項鎖定之間的階層關係。如果您不需要建立鎖定的階層,您可以實作 contains 方法來簡單呼叫 isConflicting

以下是控制 java.io.File 控點之寫入權的階層鎖定範例。

      public class FileLock implements ISchedulingRule {
      private String path;
      public FileLock(java.io.File file) {
         this.path = file.getAbsolutePath();
      }
      public boolean contains(ISchedulingRule rule) {
         if (this == rule)
               return true;
         if (rule instanceof FileLock)
            return path.startsWith(((FileLock) rule).path);
         if (rule instanceof MultiRule) {
            MultiRule multi = (MultiRule) rule;
            ISchedulingRule[] children = multi.getChildren();
            for (int i = 0; i < children.length; i++)
               if (!contains(children[i]))
            return false;
               return true;
            }
            return false;
      }
      public boolean isConflicting(ISchedulingRule rule) {
         if (!(rule instanceof FileLock))
            return false;
         String otherPath = ((FileLock)rule).path;
         return path.startsWith(otherPath) || otherPath.startsWith(path);
      }
   }

如果執行緒試圖在已經擁有一個規則時取得第二個規則,contains 方法就會開始作用。如果要避免死結的可能性,在任何給定的時間,任何給定的執行緒都只能擁有一個排程規則。如果執行緒在已擁有規則時呼叫 beginRule,不論是透過先前的 beginRule 呼叫或藉由執行含排程規則的工作, 這時都會諮詢 contains 方法來查看這兩個規則相不相等。如果已擁有的規則的 contains 方法傳回 truebeginRule 呼叫就會順利完成。如果 contains 方法傳回 false,就會發生錯誤。

用更具體的詞彙來說,比方說,執行緒在位於 "c:\temp" 的目錄上擁有我們的範例 FileLock 規則。當它擁有這個規則時,它只能修改這個目錄子樹內的檔案。如果它試圖修改不在 "c:\temp" 之下的其他目錄中的檔案,它應該會失敗。因此,排程規則就是工作或執行緒能或不能執行什麼的具體規格。違反這個規格會造成執行時期異常狀況。在並行文獻中,這種技術稱為兩段式鎖定。在兩段式鎖定的架構中,程序必須事先指定它在特定作業上所需要的所有鎖定,而且,之後在作業期間,便不能取得進一步的鎖定。兩段式鎖定消除了循環等待死結所必然的保留並等待的狀況。因此,只用排程規則來作為鎖定基本元素的系統,不可能進入死結的情況。