Regels plannen

Jobplanningsregels worden gebruikt om te bepalen wanneer jobs worden uitgevoerd ten opzichte van andere jobs. Met planningsregels kunt u met name voorkomen dat meerdere jobs tegelijkertijd worden uitgevoerd in situaties waar dit tot inconsistente resultaten kan leiden. Ook kunt u met planningsregels de uitvoeringsvolgorde van een reeks jobs bepalen. U leert de kracht van planningsregels het best kennen aan de hand van een voorbeeld. Om te beginnen worden twee jobs gedefinieerd die worden gebruikt om tegelijkertijd een lichtschakelaar aan en uit te zetten.

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

We schrijven nu een eenvoudig programma dat een lichtschakelaar maakt, deze aanzet en vervolgens weer uit.

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

Als u dit programmaatje vaak genoeg uitvoert, krijgt u uiteindelijk de volgende uitvoer:

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

Hoe is dit mogelijk? Er zijn instructies gegeven om het licht aan te zetten en vervolgens weer uit, dus de laatste status moet uit zijn! Het probleem is dat er geen voorziening is om te voorkomen dat de job LightOff tegelijk actief is met de job LightOn. Hoewel de job "on" als eerste is gepland, is het door de gelijktijdige niet mogelijk de exacte volgorde van uitvoering van de jobs vast te stellen. Als de job LightOff eerder wordt uitgevoerd dan de job LightOn job, is het resultaat ongeldig. U moet kunnen voorkomen dat twee jobs tegelijkertijd worden uitgevoerd en juist hiervoor zijn planningsregels bedoeld.

U kunt het probleem in het voorbeeld oplossen met een eenvoudige planningsregel die fungeert als mutex (ook bekend als binaire semafoor):

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

Vervolgens voegt u deze regel toe aan de twee lichtschakelaarjobs uit het vorige voorbeeld:

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

Als de twee lichtschakelaarjobs worden gepland, roept de jobinfrastructuur de methode isConflicting aan om de planningsregels van de jobs te vergelijken. Er wordt geconstateerd dat de planningsregels van de twee jobs in conflict zijn en gezorgd dat ze in de juiste volgorde worden uitgevoerd. De jobinfrastructuur zorgt ook dat ze nooit tegelijkertijd kunnen worden uitgevoerd. Als u nu het voorbeeldprogramma een miljoen keer uitvoert, krijgt u altijd hetzelfde resultaat:

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

Regels kunnen ook onafhankelijk van jobs worden gebruikt als algemeen vergrendelingsmechanisme. In het volgende voorbeeld wordt een regel opgehaald in een try-catchblok, waardoor andere threads en jobs niet met die regel kunnen worden uitgevoerd tussen de aanroep van beginRule en endRule.

   IJobManager manager = Platform.getJobManager();
  try {
      manager.beginRule(rule, monitor);
      ... wat werk doen ...
   } finally {
      manager.endRule(rule);
   }

U moet buitengewoon voorzichtig zijn als u planningsregels verkrijgt en vrijgeeft met een dergelijk coderingspatroon. Als u de regel waarvoor u beginRule hebt aangeroepen niet afsluit, wordt de regel blijvend vergrendeld.

Eigen regels maken

Hoewel de job-API het contract van planningsregels definieert, biedt deze geen feitelijke planningsregelimplementaties. In principe kan de generieke infrastructuur niet weten welke jobsets tegelijkertijd mogen worden uitgevoerd. Jobs hebben standaard geen planningsregels en een geplande job wordt zo snel uitgevoerd als de thread gemaakt kan worden.

Als een taak een planningsregel heeft, wordt de methode isConflicting gebruikt om vast te stellen of de regel in conflict is met de regels van jobs die momenteel actief zijn. Uw implementatie van isConflicting kan exact definiëren wanneer het veilig is om uw job uit te voeren. In het lichtschakelaarvoorbeeld gebruikt de isConflicting-implementatie gewoon een identiteitsvergelijking met de verstrekte regel. Als een andere job een identieke regel heeft, worden de jobs niet tegelijkertijd uitgevoerd. Als u uw eigen planningsregels schrijft, moet u het API-contract voor isConflicting lezen en navolgen.

Als de job verschillende niet-gerelateerde voorwaarden heeft, kunt u meerdere planningsregels tegelijk opstellen met een MultiRule. Als uw job bijvoorbeeld een lichtschakelaar aan moet zetten en ook informatie naar een netwerksocket moet schrijven, kan de job een regel krijgen voor de lichtschakelaar en een regel voor schrijftoegang tot de socket. Deze kunnen tot één regel worden gecombineerd met de methode MultiRule.combine.

Regelhiërarchieën

De methode isConflicting voor ISchedulingRule is eerder besproken, maar dat is nog niet het geval voor de methode contains. Deze methode wordt gebruikt voor een vrij gespecialiseerde toepassing van planningsregels, die veel gebruikers waarschijnlijk niet nodig hebben. Planningsregels kunnen logisch zijn opgebouwd in hiërarchieën voor het beheer van de toegang tot van nature hiërarchische resources. Het eenvoudigste voorbeeld om dit concept duidelijk te maken, is de boomstructuur van een bestandssysteem. Als een toepassing een exclusieve vergrendeling van een directory probeert te verkrijgen, betekent dit meestal ook dat de toepassing exclusieve toegang tot de bestanden en subdirectory's wil hebben. De methode contains wordt gebruikt om de hiërarchische relatie tussen vergrendelingen op te geven. Als u geen hiërarchie voor vergrendelingen hoeft te bouwen, kunt u de methode contains implementeren om gewoon isConflicting aan te roepen.

Hier volgt een voorbeeld van een hiërarchische vergrendeling voor het beheer van schrijftoegang tot java.io.File-handles.

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

De methode contains komt om de hoek kijken als een thread probeert een tweede regel te verkrijgen als deze er al een heeft. Om de mogelijkheid van een conflict uit te sluiten, kan een thread op een bepaald moment eigenaar zijn van slechts één planningsregel. Als een thread beginRule aanroept als hij al een planningsregel in bezit heeft (door een eerdere aanroep van beginRule of door een job uit te voeren met een planningsregel), wordt met de methode contains gecontroleerd of de twee regels gelijk zijn. Als de methode contains als resultaat true retourneert voor de regel die al een eigenaar heeft, slaagt de aanroep van beginRule. Als de methode contains false retourneert, treedt er een fout op.

Stel dat een thread eigenaar is van de FileLock-regel uit het voorbeeld, in de directory "c:\temp". De thread is wel eigenaar, maar mag alleen bestanden bewerken in die subdirectory. Als de thread probeert bestanden te bewerken in andere directory's die niet onder "c:\temp" staan, zal dit niet lukken. Op deze manier is een planningsregel een concrete specificatie van wat een job of thread mag of niet mag. Het overtreden van de specificatie leidt tot een runtime-uitzondering. In de literatuur over gelijktijdige verwerking wordt deze techniek tweefasevergrendeling genoemd. In een schema voor tweefasevergrendeling moet een proces van tevoren alle vergrendelingen voor een bepaalde taak opgeven. Hierna mag het proces geen vergrendelingen meer aanvragen tijdens de bewerking. Tweefasevergrendeling sluit de voorwaarde aanhouden-en-wachten uit. Deze is vereist bij wachtconflicten tussen meer dan twee items. Het is niet mogelijk dat een systeem alleen planningsregels als vergrendeling gebruikt om conflicten op te lossen.