Schemaläggningsregler

Du kan använda schemaläggningsregler för jobb till att styra när jobben ska köras i förhållande till andra jobb. Schemaläggningsregler ger framför allt möjlighet att förhindra att flera jobb körs samtidigt i situationer där samtidighet kan leda till inkonsekventa resultat. Det ger också möjlighet att säkerställa körningsordningen för en serie av jobb. Schemaläggningsreglernas styrka visas bäst med ett exempel. Vi börjar med att definiera två jobb som används till att tända och släcka en lampa samtidigt:

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

Nu skapar vi ett enkelt program som skapar en strömbrytare och slår på och av den:

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

Om vi kör det här lilla programmet tillräckligt många gånger får vi till slut följande utdata:

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

Hur kommer det sig ? Vi angav att lampan skulle slås på och sedan av, så det slutliga läget borde vara av! Problemet är att det inte fanns något som förhindrar att jobbet LightOff körs samtidigt som jobbet LightOn. Även om jobbet "on" schemalades först innebär den samtidiga körningen att det inte finns något sätt att förutsäga den exakta körningsordningen för de två samtidiga jobben. Om körningen av jobbet LightOff slutförs före jobbet LightOn får vi det här ogiltiga resultatet. Vad vi nu behöver är ett sätt att förhindra att de två jobben körs samtidigt, och det är här schemaläggningen kommer in i bilden.

Vi kan rätta till det här exemplet genom att skapa en enkel schemaläggningsregel som fungerar som ett mutex (även kallat binär semafor):

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

Den här regeln läggs sedan till i de två strömbrytarjobben från vårt tidigare exempel:

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

När vi nu har schemalagt de två strömbrytarjobben kommer jobbinfrastrukturen att anropa metoden isConflicting för att jämföra schemaläggningsreglerna för jobben. Det kommer att framgå att de två jobben har schemläggningsregler som står i konflikt med varandra, och metoden kommer att säkerställa att de körs i rätt ordning. Den kommer också att säkerställa att de aldrig körs samtidigt. Om vi nu kör exempelprogrammet en miljon gånger kommer vi alltid att få samma resultat:

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

Det går också att använda regler utan samband med jobb, som en allmän låsmekanism. I följande exempel hämtas en regel inom ett try/finally-block, vilket förhindrar att andra trådar och jobb körs med den regeln under perioden mellan anropen av beginRule och endRule.

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

Du måste vara extremt försiktig när du hämtar och frigör schemaläggningsregler med ett sådant kodmönster. Om du inte avbryter en regel där du har anropat beginRule kommer regeln att vara låst för alltid.

Skapa egna regler

Trots att jobb-APIt definierar kontrakt för schemaläggningsregler tillhandahåller det inte några implementeringar av schemaläggningsregler. I den allmänna infrastrukturen finns det egentligen inget sätt att veta vilka jobbuppsättningar som kan köras samtidigt. Som standard har jobben inga schemaläggningsregler, och ett schemalagt jobb körs så snart en tråd kan skapas till att köra det.

När ett jobb har en schemaläggningsregel används metoden isConflicting till att fastställa om regeln står i konflikt med reglerna för andra jobb som körs samtidigt. Därför kan din implementering av isConflicting definiera exakt när det är säkert att köra jobbet. I vårt strömbrytarexempel använder implementeringen isConflicting en ID-jämförelse med den tillhandahållna regeln. Om något annat jobb har en identisk regel kommer de inte att köras samtidigt. När du skriver egna schemaläggningsregler är det viktigt att du noga läser och följer API-kontraktet för isConflicting.

Om ditt jobb har flera regler som inte är relaterade till de här kan du skapa flera schemaläggningsregler tillsammans genom att använda MultiRule. Om ditt jobb till exempel har till uppgift att slå på en strömbrytare och dessutom skriva information till en nätverkssockel kan det ha en regel för strömbrytaren och en regel för skrivaccess till sockeln. De reglerna kan kombineras till en enda med fabriksmetoden MultiRule.combine.

Hierarkier för regler

Vi har diskuterat metoden isConflictingISchedulingRule, men ännu har vi inte nämnt metoden contains. Den metoden används till en ganska specialiserad tillämpning av schemaläggningregler som många klienter inte behöver. Schemaläggningsregler kan sättas samman i hierarkier för styrning av access till resurser med naturliga hierarkier. Det enklaste sättet att exemplifiera det här begreppet är att tänka sig ett trädbaserat filsystem. Om en tillämpning ska hämta ett exklusivt lås till en katalog förutsätts det vanligtvis att tillämpningen också ska ha access till filerna och underkatalogerna i katalogen. Metoden contains används till att ange den hierarkiska relationen mellan låsen. Om du inte behöver skapa hierarkier för lås kan du implementera metoden contains och helt enkelt anropa isConflicting.

Här är ett exempel på ett hierarkiskt lås för att styra skrivaccessen till java.io.File-handtagen.

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

Metoden contains kommer in i bilden om en tråd försöker hämta en andra regel när den redan äger en regel. För att undvika risken att det uppstår dödlägen kan en viss tråd bara äga en schemaläggningsregel åt gången. Om en tråd anropar beginRule när den redan äger en regel, antingen genom ett tidigare anrop till beginRule eller genom att köra ett jobb med en schemaläggningsregel, används metoden contains till att ta reda på om de två reglerna är desamma. Om metoden contains för den regel som redan ägs returnerar true kommer anropet av beginRule att lyckas. Om metoden contains returnerar false uppstår ett fel.

Vi kan konkretisera det genom att anta att en tråd äger vår exempelregel FileLock för katalogen i "c:\temp". Medan den äger regeln kan den bara ändra filer inom den katalogens underordnade träd. Om den försöker ändra filer i andra kataloger som inte finns under "c:\temp" misslyckas det. En schemaläggningsregel är alltså en konkret specifikation av vad ett jobb eller en tråd kan göra och inte göra. Brott mot den specifikationen leder till ett runtime-undantag. Den här tekniken brukar kallas tvåfaslåsning. I ett schema för tvåfaslåsning måste en process ange i förväg vilka lås som behövs för en viss uppgift. Sedan går det inte att hämta fler lås under operationen. Med tvåfaslåsning elimineras det tillstånd med uppehåll och väntan som är en förutsättning för cirkulärt dödläge. Därför är det omöjligt för ett system där endast schemaläggningsregler används som låsgrund att komma in i ett dödläge.