Ütemezési szabályok

A feladat ütemezési szabályok segítségével szabályozható, hogy a feladatok mikor fussanak a többi feladathoz képest. Az ütemezési szabályok segítségével megakadályozhatja, hogy több feladat fusson egyidejűleg olyan helyzetben, ahol a konkurencia inkonzisztens eredményt okozhat. Segítségükkel a feladatok végrehajtási sorrendje is garantálható. Az ütemezési szabályok ereje legjobban egy példával szemléltethető. Két feladat megadásával kezdjük, amelyek a fényt egyidejűleg ki- és bekapcsolják:

      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("A fény felkapcsolása");
         }
         public IStatus run(IProgressMonitor monitor) {
            System.out.println("A fény felkapcsolása");
            isOn = true;
            return Status.OK_STATUS;
         }
      }
      class LightOff extends Job {
         public LightOff() {
            super("A fény lekapcsolása");
         }
         public IStatus run(IProgressMonitor monitor) {
            System.out.println("A fény lekapcsolása");
            isOn = false;
            return Status.OK_STATUS;
         }
      }
   }

Ezután létrehozunk egy egyszerű programot, amely létrehoz egy kapcsolót, és ki- illetve bekapcsolja a fényt újra:

      LightSwitch light = new LightSwitch();
   light.on();
   light.off();
   System.out.println("A fény be van kapcsolva? " + switch.isOn());

Ha elégszer lefuttatjuk ezt a kis programot, akkor végül az alábbi kimenetet kapjuk:

      A fény kikapcsolása
   A fény bekapcsolása
   A fény be van kapcsolva? igaz

Ez hogyan lehetséges? A fényt be- majd kikapcsoltuk, és így végső állapotnak a kikapcsoltnak kell lennie! A probléma az, hogy semmi nem akadályozza, hogy a LightOff feladat a LightOn feladattal egyszerre fusson. Annak ellenére, hogy a "bekapcsolás" feladat volt először ütemezve, az egyidejű végrehajtás azt jelenti, hogy nincs lehetőség a két konkurens feladat pontos végrehajtási sorrendjének megjósolására. Ha a LightOff feladat a LightOn feladat előtt befejezi a futást, akkor ezt az érvénytelen eredményt kapjuk. Ezért meg kell akadályozni, hogy két feladat egyidejűleg fusson, és itt lépnek képbe az ütemezési szabályok.

Ezt a példát egy egyszerű ütemezési szabály létrehozásával kijavíthatjuk, amely kölcsönös kizárásként működik (bináris szemafornak is hívják):

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

Ez a szabály ezután hozzáadódik az előző példa két kapcsoló-feladatához:

      public class LightSwitch {
      final MutextRule rule = new MutexRule();
      ...
      class LightOn extends Job {
         public LightOn() {
            super("A fény felkapcsolása");
            setRule(rule);
         }
         ...
      }
      class LightOff extends Job {
         public LightOff() {
            super("A fény lekapcsolása");
            setRule(rule);
         }
         ...
      }
   }

Ha a két kapcsoló-feladat ütemezve van, akkor a feladat-infrastruktúra meghívja az isConflicting metódust a két feladat ütemezési szabályának összehasonlítása érdekében. Ez jelzi, hogy a két feladat ütköző ütemezési szabályokkal rendelkezik és biztosítja, hogy megfelelő sorrendben fussanak le. Arról is gondoskodik, hogy sose fussanak egyidejűleg. Ha a példaprogramot milliószor lefuttatja, akkor mindig ugyanazt az eredményt kapja:

      A fény bekapcsolása
   A fény kikapcsolása
   A fény be van kapcsolva? hamis

A szabályok a feladatoktól függetlenül használhatók általános zárolási mechanizmusként. Az alábbi példa szerez egy szabályt a próba/végső blokkolás között, amely megakadályozza, hogy más szálak és feladatok fussanak ezzel a szabállyal a beginRule és endRule meghívása között.

      IJobManager manager = Platform.getJobManager();
   try {
      manager.beginRule(rule, monitor);
      ... végrehajt valami feladatot ...
   } finally {
      manager.endRule(rule);
   }

Különös kerültekintéssel kell eljárnia az ütemezési szabályok kódolási minták segítségével lefoglalásakor és felszabadításakor. Ha nem tudja befejezni a szabályt, amelyhez meghívta a beginRule metódust, akkor ezt a szabályt örökre zárolta.

Saját szabályok létrehozása

A feladat API megadja az ütemezési szabályok megegyezését, de valójában nem biztosít ütemezési szabály-megvalósításokat. Az általános infrastruktúra nem tudja, hogy milyen feladathalmazok tudnak megfelelően futni. Alapértelmezés szerint a feladatok nem rendelkeznek ütemezési szabályokkal, és az ütemezett szabály végrehajtásra kerül, amint a szál létrehozható a futtatáshoz.

Ha a feladat rendelkezik ütemezési szabállyal, akkor az isConflicting metódus meghatározza, hogy a szabály összeütközik-e az egyidejűleg futó feladatok szabályaival. Az isConflicting megvalósítása pontosan meghatározza, hogy mikor biztonságos a feladat végrehajtása. A kapcsolópéldában az isConflicting megvalósítás egyszerűen használ egy azonosság-összehasonlítást a biztosított szabállyal. Ha másik feladat is rendelkezik egy megegyező szabállyal, akkor nem fog megfelelően futni. Saját ütemezési szabályok írásakor győződjön meg róla, hogy olvasható, és kövesse az API megegyezést az isConflicting metódushoz körültekintően.

Ha a feladat számos független megszorítással rendelkezik, akkor létrehozhat egyszerre több ütemezési szabályt a MultiRule segítségével. Ha például fel kell kapcsolnia a fényt, és információkat kell írni a hálózati socketbe, akkor rendelkezhet egy szabállyal a kapcsolóhoz és eggyel a socket írási hozzáféréséhez, amelyek egy szabályba vannak egyesítve a MultiRule.combine gyári metódus segítségével.

Szabályhierarchiák

Az isConflicting metódust bemutattuk az ISchedulingRule szabályon, de a contains metódust eddig nem említettük. Ezt a metódust az ütemezési szabályok egy teljesen specializált alkalmazása használja, amelyet számos ügyfél nem igényel. Az ütemezési szabályok logikailag hierarchiákba szervezhetők az eredetileg hierarchikus erőforrások eléréséhez. A legegyszerűbb példa ennek szemléltetésére egy fa alapú fájlrendszer. Ha az alkalmazás kizárólagos zárolást kíván szerezni a könyvtárra, akkor ez jellemzően magába foglalja, hogy a könyvtár fájljaihoz és alkönyvtáraihoz is kizárólagos zárolásra van szükség. A contains metódus megadja a zárolások közötti hierarchikus kapcsolatot. Ha nem kell zárolási hierarchiát létrehozni, akkor megvalósíthatja a contains metódust az isConflicting egyszerű meghívásához.

Az alábbiakban látható egy példa a hierarchikus zárolásra a java.io.File kezelők írási hozzáférésének szabályozásához.

      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);
      }
   }

A contains metódus akkor kerül elő, ha a szál megpróbál egy második szabályt szerezni, amikor már birtokol egy szabályt. A holtpont lehetőségének elkerülése érdekében egy adott szál csak egy ütemezési szabályt birtokolhat egy adott időben. Ha a szál meghívja a beginRule metódust, amikor már birtokol egy szabályt a korábbi beginRule hívás miatt vagy a feladat ütemezési szabállyal végrehajtása miatt, akkor a contains metódus megnézi, hogy a két szabály megegyezik-e. Ha a már birtokolt szabály contains metódusa true értéket ad vissza, akkor a beginRule meghívás sikeres lesz. Ha a contains metódus false eredményt ad vissza, akkor hiba történik.

Konkrétabban mondjuk azt, hogy a szál birtokolja a példa FileLock szabályt a könyvtáron a "c:\temp" címen. Amíg birtokolja ezt a szabályt, csak a könyvtár alfában lévő fájlokat módosíthatja. Ha megpróbál egy másik könyvtárban lévő fájlt módosítani, amely a "c:\temp" alatt található, akkor ez meg fog hiúsulni. Az ütemezési szabály egy konkrét specifikációja annak, hogy mely feladat vagy szál engedélyezett vagy nem engedélyezett. A specifikáció megsértése futási kivételt okoz. A konkurenciával foglalkozó irodalom ezt a technikát kétfázisos zárolásnak nevezi. A kétfázisos zárolási sémában a folyamat előzetesen megadja az összes zárolást, amely szükséges egy adott feladathoz, és a művelet során nem szerezhet további zárolásokat. Kétfázisos zárolás kiküszöböli a tart-és-vár helyezetet, amely a körkörös várakozási holtpont előfeltétele. Nem lehetséges, hogy a rendszer az ütemezési szabályokat csak zárolási primitívként használja a holtpontba belépéshez.