Ajoitussäännöt

Työn ajoitussääntöjen avulla voi hallita sitä, milloin työ ajetaan suhteessa muihin töihin. Ajoitussäännöillä voi estää varsinkin sen, että useat työt ajetaan samaan aikaan tilanteissa, joissa samanaikaisuus voi johtaa ristiriitaisiin tuloksiin. Ne myös varmistavat töiden ajojärjestyksen. Ajoitussääntöjen etuja voi esitellä parhaiten esimerkin avulla. Seuraavassa määritetään aluksi kaksi työtä, joiden avulla valokatkaisimen virta kytketään ja katkaistaan samanaikaisesti:

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

Seuraavaksi luodaan yksinkertainen ohjelma, joka luo valokatkaisimen ja kytkee virran tai katkaisee sen:

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

Jos tämä ohjelma ajetaan riittävän monta kertaa, saadaan lopulta seuraava tuloste:

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

Kuinka tämä on mahdollista? Valohan kytkettiin ja sitten katkaistiin, joten sen lopullisen tilan pitäisi olla katkaistuna. Ongelma on siinä, että mikään ei estä LightOff-työn ajoa samaan aikaan LightOn-työn kanssa. Näin ollen, vaikka "on"-työ on ajoitettu ensin, niiden samanaikaisesta toteutuksesta seuraa, että on mahdotonta ennustaa töiden tarkkaa suoritusjärjestystä. Jos LightOff-työ ajetaan ennen LightOn-työtä, seurauksena on tämä virheellinen tulos. Tarvitaan keino estää kahden työn ajo samanaikaisesti, ja tässä voidaan käyttää ajoitussääntöjä.

Tämän esimerkin voi korjata luomalla yksinkertaisen ajoitussäännön, joka toimii mutex-toimintona (tunnetaan myös binaarisena opastimena):

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

Tämä sääntö lisätään sitten edellisen esimerkin kahteen valokatkaisintyöhön:

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

Kun valokatkaisintyöt nyt ajoitetaan, työn rakenne kutsuu isConflicting-metodia, jonka avulla verrataan töiden ajoitussääntöä. Se huomaa, että töillä on ristiriitaiset ajoitussäännöt, ja varmistaa, että ne ajetaan oikeassa järjestyksessä. Se varmistaa myös, että niitä ei koskaan ajeta samaan aikaan. Nyt esimerkkiohjelman voi ajaa kuinka monta kertaa tahansa, mutta tulos on aina sama:

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

Sääntöjä voi käyttää myös töistä riippumattomana yleisenä lukitusmenetelmänä. Seuraava esimerkki hankkii säännön try/finally-lohkossa, mikä estää muiden säikeiden ja töiden ajon kyseisen säännön kanssa beginRule- ja endRule-kutsujen välillä.

   IJobManager manager = Platform.getJobManager();
   try {
      manager.beginRule(rule, monitor);
      ... tee jokin työ ...
   } finally {
      manager.endRule(rule);
   }

Ajoitussääntöjen hankkiminen ja vapauttaminen tällaisessa koodauksessa edellyttää äärimmäistä varovaisuutta. Jos et lopeta sääntöä, jolla olet tehnyt beginRule-kutsun, sääntö on lukittu pysyvästi.

Omien sääntöjen laatiminen

Vaikka työn sovellusohjelmaliittymä määrittää ajoitussäännöt, se ei varsinaisesti anna ajoitussääntöjen toteutuksia. Yleisrakenteella ei ole mitään keinoa tietää, mitkä työjoukot voi ajaa samanaikaisesti. Oletusarvon mukaan töillä ei ole ajoitussääntöjä, ja ajoitettu työ toteutetaan niin pian kuin sen ajamiseksi ehditään luoda säie.

Kun työllä on ajoitussääntö, isConflicting-metodin avulla selvitetään, onko sääntö ristiriidassa ajossa olevien töiden sääntöjen kanssa. Siksi isConflicting-toteutus voi selvittää, milloin on turvallista ajaa työ. Valokatkaisinesimerkissä isConflicting-toteutus käyttää yksinkertaisesti tunnistevertailua annetun säännön kanssa. Jos toisella työllä on samanlainen sääntö, niitä ei ajeta samanaikaisesti. Kun kirjoitat omat ajoitussäännöt, lue huolellisesti isConflicting-metodin sovellusohjelmaliittymän ohjeet ja noudata niitä.

Jos työssä on useita toisiinsa liittymättömiä rajoitteita, voit koostaa useita ajoitussääntöjä yhteen MultiRule-kohteen avulla. Jos työn on esimerkiksi kytkettävä ja katkaistava valo sekä kirjoitettava tietoja verkkovastakkeeseen, sillä voi olla sääntö valokatkaisinta varten ja sääntö vastakkeeseen kirjoitusta varten yhdistettynä yhdeksi säännöksi MultiRule.combine-factory-metodin avulla.

Sääntöhierarkiat

Edellä on käsitelty isConflicting-metodia ISchedulingRule-kohteen yhteydessä, mutta toistaiseksi ei ole käsitelty contains-metodia. Tämän metodin avulla ajoitussääntöjä sovelletaan erikoistarkoitukseen, jota useimmat asiakkaat eivät tarvitse. Ajoitussäännöt voi koota loogisiin hierarkioihin, joiden avulla voi hallita luontaisesti hierarkkisten resurssien käyttöä. Yksinkertaisin tapa havainnollistaa tätä on rakenteinen tiedostojärjestelmä. Jos sovellus haluaa yksityislukituksen hakemistoon, se yleensä haluaa yksityisoikeuden myös hakemiston tiedostoihin ja alihakemistoihin. Metodin contains avulla määritetään lukitusten hierarkkiset suhteet. Jos ei ole tarpeen luoda lukitusten hierarkioita, voit yksinkertaisesti kutsua isConflicting-metodia toteuttamalla contains-metodin.

Seuraavassa on esimerkki hierarkkisesta lukituksesta, jonka avulla hallitaan java.io.File-kahvojen kirjoitusoikeutta.

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

Metodi contains tulee toimintaan, jos säie yrittää hankkia toisen säännön, kun sillä on jo yksi. Jotta lukkiutuman mahdollisuus vältettäisiin, kullakin säikeellä voi olla vain yksi ajoitussääntö kerrallaan. Jos säie kutsuu beginRule-kohdetta, kun sillä on jo sääntö joko aiemman beginRule-kutsun takia tai ajoitussäännön sisältävän työn ajon takia,contains-metodin avulla tarkistetaan, vastaavatko säännöt toisiaan. Jos contains-metodi palauttaa aiemman säännön osalta arvon true, beginRule-kutsu onnistuu. Jos contains-metodi palauttaa arvon false, aiheutuu virhe.

Kuvitellaan konkreettisena esimerkkinä tilanne, jossa säikeellä on esimerkin FileLock-sääntö hakemistoon "c:\temp". Vaikka se omistaa säännön, se voi muokata vain tiedostoja, jotka ovat hakemiston alirakenteessa. Jos se yrittää muokata tiedostoja hakemistoissa, jotka eivät ole hakemiston "c:\temp" alirakenteessa, yritys ei onnistu. Näin ollen ajoitussääntö on konkreettinen määritys siitä, mitä työ tai säie voi tai ei voi tehdä. Tämän määrityksen loukkaaminen aiheuttaa ajonaikaisen poikkeuksen. Samanaikaisuutta käsittelevässä kirjallisuudessa tämä tekniikka tunnetaan nimellä kaksivaiheinen lukitus (two-phase locking). Kaksivaiheisessa lukitussuunnitelmassa prosessin on määritettävä ennalta kaikki tiettyyn tehtävään tarvitsemansa lukitukset eikä se sen jälkeen saa muita lukituksia toiminnan aikana. Kaksivaiheinen lukitus poistaa odotusehdon, joka on ennakkoedellytys kehämäisen odotuksen lukkiutumalle. Siten on mahdotonta, että pelkkiä ajoitussääntöjä lukitusprimitiivinä käyttävä järjestelmä joutuisi lukkiutumaan.