Planlægningsregler for job kan bruges til at styre, hvornår job skal udføres i forhold til andre job. Med planlægningsregler kan du forhindre, at flere job udføres samtidig i situationer, hvor samtidighed kan føre til inkonsistente resultater. De giver dig også mulighed for at sikre udførelsesrækkefølgen af en række job. Virkningen af planlægningsregler illustreres bedst ved et eksempel. Start med at definere to job, der skal bruges til at tænde og slukke en lyskontakt 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("Tænder lyset"); } public IStatus run(IProgressMonitor monitor) { System.out.println("Tænder lyser"); isOn = true; return Status.OK_STATUS; } } class LightOff extends Job { public LightOff() { super("Slukker lyset"); } public IStatus run(IProgressMonitor monitor) { System.out.println("Slukker lyset"); isOn = false; return Status.OK_STATUS; } } }
Du skal nu oprette en enkelt program, der opretter en lyskontakt, og som tænder og slukker den igen:
LightSwitch light = new LightSwitch(); light.on(); light.off(); System.out.println("Er lyset tændt? " + switch.isOn());
Hvis du udfører dette lille program tilstrækkelige mange gange, får du følgende output:
Slukker lyset Tænder lyset Er lyset tændt? true
Hvordan kan det være? Programmet bad om, at lyset skulle tændes og derefter slukkes. Den endelige tilstand bør derfor være slukket. Problemet skyldes, at der ikke er noget, der forhindrer jobbet LightOff i at udføres samtidigt med jobbet LightOn. Så selvom jobbet, der "tænder", er planlagt til at udføres først, betyder deres samtidige udførelse, at der ikke er nogen måde at forudsige den præcise rækkefølge af de to job på. Hvis jobbet LightOff ender med at udføres inden jobbet LightOn job, får vi et ugyldigt resultat. Der er brug for en måde, der forhindrer, at de to job udføres samtidigt, og det er, hvad planlægningsregler skal bruges til.
Du kan rette dette eksempel ved at tilføje en enkelt planlægningsregel, der fungerer som en mutex (også kaldet 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 regel tilføjes til de to lyskontaktjob fra det tidligere eksempel:
public class LightSwitch { final MutextRule rule = new MutexRule(); ... class LightOn extends Job { public LightOn() { super("Tænder lyset"); setRule(rule); } ... } class LightOff extends Job { public LightOff() { super("Slukker lyset"); setRule(rule); } ... } }
Når de to lyskontaktjob planlægges nu, kalder jobinfrastrukturen metoden isConflicting for at sammenligne planlægningsreglerne for de to job. Metoden registrerer, at de to job har modstridende planlægningsregler og sørger for, at de udføres i den korrekte rækkefølge. Den sikrer også, at de aldrig udføres samtidigt. Når du udfører programeksemplet nu, får du altid det samme resultat:
Tænder lyset Slukker lyset Er lyset tændt? false
Regler kan også bruges uafhængigt af job som en generel låsemekanisme. I følgende eksempel hentes en regel i en try/finally-blok, som forhindrer andre programdele og job i at blive udført pga. den regel for varighed, der gælder mellem start af beginRule og endRule.
IJobManager manager = Platform.getJobManager(); try { manager.beginRule(rule, monitor); ... udfør noget arbejde ... } finally { manager.endRule(rule); }
Du skal være meget opmærksom, når du henter og frigiver planlægningsregler vha. et kodningsmønster. Hvis du undlader at afslutte en regel, som du har kaldt beginRule for, er reglen låst fremover.
Selvom job-API'et definerer kontrakten for planlægningsreglerne, stiller den ikke nogen egentlige planlægningsregelimplementeringer til rådighed. Den generiske infrastruktur kan på ingen vide, hvilke sæt job det er i orden af udføre samtidigt. Som standard har job ingen planlægningsregler, og et planlagt job udføres lige så hurtigt, som en programdel til at udføre det kan oprettes.
Når et job har en planlægningsregel, bruges metoden isConflicting til at bestemme, om reglen er i konflikt med reglerne for et andet job, der aktuelt udføres. Implementationen af isConflicting kan derfor definere præcist, hvornår det er sikkert at udføre jobbet. I eksemplet med lyskontakten bruger isConflicting-implementationen blot en identitetssammenligning med den leverede regel. Hvis et andet job har den samme regel, udføres jobbene ikke samtidigt. Når du selv skriver planlægningsregler, skal du sikre dig, at du har læst og følger API-kontrakten for isConflicting nøje.
Hvis jobbet har flere ikke-relaterede betingelser, kan du sammensætte flere planlægningsregler vha. en MultiRule. Hvis jobbet f.eks. skal tænde en lyskontakt og skrive oplysninger til en netværkssokkel, kan der være én regel for lyskontakten og én regel for skriveadgangen til netværkssoklen, og disse kan kombineres i en enkelt regel vha. fabriksmetoden MultiRule.combine.
Metoden isConflicting er gennemgået i ISchedulingRule, men det er metoden contains ikke. Denne metode benyttes til en særlig anvendelse af planlægningsregler, som mange klienter ikke får brug for. Planlægningsregler kan sammensættes logisk i hierarkier til brug for styring af adgangen til naturlige hierarkiske ressourcer. Dette koncept kan illustreres bedst ved et filsystem, der er baseret på træstrutkur. Hvis et program ønsker at få en eksklusiv lås på et bibliotek, betyder det typisk, at det også ønsker eksklusiv adgang til filerne og underbibliotekerne i det pågældende bibliotek. Metoden contains bruges til at angive den hierarkiske relation mellem låse. Hvis du ikke har brug for at oprette låsehierarkier, kan du implementerer metoden contains til blot at kalde isConflicting.
Her er et eksempel på en hierarkisk lås til brug for styring af skriveadgangen til java.io.File-referencerne.
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 bliver relevant, hvis en programdel forsøger at hente en yderligere regel, når den allerede ejer en regel. For at undgå muligheden for baglås kan en given programdel kun eje en planlægningsregel på et givent tidspunkt. Hvis en programdel kalder beginRule, mens den allerede ejer en regel enten via et tidligere kald til beginRule eller ved udførelsen af et job med en planlægningsregel, konsulteres metoden contains for at undersøge, om de to regler er ens. Hvis metoden contains for den regel, der allerede ejes, returnerer true, startes beginRule. Hvis metoden contains returnerer false, opstår en fejl.
Antag, at en programdel ejer regeleksemplet FileLock på biblioteket i "c:\temp". Mens programdelen ejer denne regel, kan den kun ændre filer i det biblioteks undertræstruktur. Hvis den forsøger at ændre filer i andre biblioteker, som ikke er under "c:\temp", opstår en fejl. Derfor er en planlægningsregel en konkret angivelse af, hvad et job eller program har tilladelse til og ikke har tilladelse til. Overskridelse af den specifikation resulterer i en runtime-undtagelse. Denne teknik kaldes tofaselåsning. I et tofaselåsningsskema skal en proces på forhånd angive alle de låse, som den får brug for til en bestemt opgave, og den må ikke få yderligere låse i løbet af funktionen. Tofaselåsning udelukker hold-and-wait-betingelsen, som er en forudsætning for cirkulær wait-baglås. Det er derfor umuligt for et system, der kun anvender planlægningsregler, at gå i baglås.