Planleggingsregler

Jobbens planleggingsregler kan brukes til å kontrollere når jobbene kjøres i forhold til andre jobber. Du kan nærmere bestemt bruke planleggingsregler til å hindre samtidig kjøring av flere jobber i situasjoner der samtidighet kan gi inkonsistent resultat. Med slike regler kan du dessuten sikre at en serie med jobber blir utført i en bestemt rekkefølge. Fordelene med planleggingsreglene illustreres best gjennom et eksempel. La oss begynne med å definere to samtidige jobber som brukes til å slå av og på en strømbryter:

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

Vi skal nå opprette et enkelt program som oppretter en strømbryter som slås av og på igjen:

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

Hvis dette programmet kjøres ofte nok, får vi til slutt følgende utdata:

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

Hvordan henger dette sammen? Vi angav at lampen skulle slås på og deretter av slik at det til slutt var avslått. Problemet er imidlertid at det ikke er noe som hindrer LightOff-jobben i å kjøre samtidig som LightOn-jobben. Så selv om "on"-jobben ble planlagt først, er det ved samtidig utføring ikke mulig å forutsi nøyaktig hvilken rekkefølge de to samtidige jobbene utføres i. Hvis kjøringen av LightOff-jobben kjøres før LightOn-jobben, får vi dette ugyldige resultatet. Vi må på en eller annen måte hindre de to jobbene i å kjøre samtidig, og det er her planleggingsregler kommer inn i bildet.

Vi kan løse dette eksempelet ved å opprette en enkel planleggingsregel som fungerer som en mutex (også kjent som en binær semafor):

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

Denne regelen legges nå til i strømbryterjobbene fra forrige eksempel:

   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å som de to strømbryterjobbene er planlagt, kaller jobbinfrastrukturen isConflicting-metoden for å sammenlikne planleggingsreglene i de to jobbene. Den oppdager at de to jobbene har motstridende planleggingsregler og forsikrer seg om at de kjøres i riktig rekkefølge. Den forsikrer seg også om at de aldri kjører samtidig. Selv om du kjører eksempelprogrammet en million ganger, får du nå alltid samme resultat:

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

Regler kan også brukes uavhengig av jobber som en generell låsemekanisme. Følgende eksempel henter en regel fra en try/finally-blokk, og hindrer andre tråder og jobber i å kjøre i perioden mellom aktivering av beginRule og endRule.

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

Du bør være svært forsiktig når du henter og frigir planleggingsregler med et slikt kodingsmønster. Hvis du ikke avslutter en regel som du har kalt med beginRule, er regelen låst for alltid.

Lage dine egne regler

Selv om programmeringsgrensesnittet for jobben definerer kontrakten for planleggingsregler, oppgir den ingen implementeringer av planleggingsregler. Den generiske infrastrukturen har ingen metode for å finne ut hvilke jobbsett som kan kjøres samtidig. Som standard har jobber ingen planleggingsregler, og en planlagt jobb utføres så snart det kan opprettes en tråd som kan kjøre den.

Når en jobb har en planleggingsregel, brukes isConflicting-metoden til å fastsette om regelen er i konflikt med reglene for jobber som kjøres for øyeblikket. Dermed kan implementeringen av isConflicting definere nøyaktig når det er trygt å utføre jobben. I eksempelet med strømbryteren bruker isConflicting-implementeringen en identitetssammenlikning med den oppgitte regelen. Hvis en annen jobb har samme regel, blir de ikke kjørt samtidig. Når du skriver dine egne planleggingsregler, må du lese nøye gjennom og følge programmeringsgrensesnittkontrakten for isConflicting.

Hvis jobben har flere utilknyttede begrensninger, kan du lage flere planleggingsregler samlet ved hjelp av en MultiRule. Hvis for eksempel jobben skal slå på en strømbryter og dessuten skrive informasjon til en nettverkskontakt, kan du lage en regel for strømbryteren og en for skrivetilgang til kontakten og slå disse sammen til en regel ved hjelp av factory-metoden MultiRule.combine.

Regelhierarkier

Vi har sett på isConflicting-metoden i ISchedulingRule, men har hittil ikke nevnt contains-metoden. Denne metoden har en nokså bestemt bruk av planleggingsregler som det kanskje sjelden er behov for. Planleggingsregler kan settes sammen i hierarkier for å kontrollere tilgangen til ressurser med naturlige hierarkier. Dette illustreres best ved å tenke seg et trebasert filsystem. Hvis en applikasjon skal ha en eksklusiv lås på en katalog, betyr dette vanligvis at den også skal ha eksklusiv tilgang til filene og delkatalogene i katalogen. Metoden contains brukes til å angi det hierarkiske forholdet mellom låser. Hvis du ikke trenger å opprette et hierarki over låser, kan du implementere contains-metoden og helt enkelt kalle isConflicting.

Her er et eksempel på en hierarkisk lås for kontroll over skrivetilgang til java.io.File-referanser.

   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 kan brukes hvis en tråd forsøker å hente en annen regel når den allerede eier en regel. For å unngå vranglås skal alle tråder bare ha en planleggingsregel om gangen. Hvis en tråd kaller beginRule når den allerede har en regel, enten gjennom et tidligere kall til beginRule eller ved å utføre en jobb med en planleggingsregel, brukes contains-metoden for å se om de to reglene er like. Hvis contains-metoden for regelen som allerede eies returnerer true, vil aktiveringen av beginRule lykkes. Hvis contains-metoden returnerer false, vil det oppstå en feil.

Dette kan vi konkretisere ved å anta at en tråd eier eksempelregelen vår, FileLock, i katalogen på "c:\temp". Når den eier denne regelen, kan den bare endre filer i det katalogdeltreet. Hvis den forsøker å endre filer i andre kataloger som ikke ligger i "c:\temp", vil den mislykkes. En planleggingsregel er med andre ord en konkret spesifisering av hva en jobb eller tråd kan eller ikke kan gjøre. Hvis denne spesifikasjonen overtres, vil det oppstå et kjøretidsunntak. Denne teknikken kalles tofaselåsing. I et skjema for tofaselåsing må det på forhånd angis alle låser som trengs for en bestemt oppgave. Senere er det ikke mulig å hente flere låser under operasjonen. Tofaselåsing eliminerer tilstanden med opphold og venting som er en forutsetning for sirkulær vranglås. Derfor er det umulig for et system som bare bruker planleggingsregler som låsemekanisme, å komme i vranglås.