作业调度规则可以用来控制作业的运行何时与其它作业有关。特别是,调度规则使您可以防止多个作业在并行可能导致不一致结果的情况下同时运行。它们还使您可以保证一系列作业的执行顺序。通过一个示例很好地说明了调度规则的作用。让我们从定义用来同时将灯打开和关闭的两个作业开始:
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; } } }
现在,创建一个简单的程序,它会创建灯开关,然后将灯打开并再次将灯关闭:
LightSwitch light = new LightSwitch(); light.on(); light.off(); System.out.println("The light is on? " + switch.isOn());
如果运行这个小程序次数足够多,最终将获得以下输出:
Turning the light off Turning the light on The light is on? true
结果将如何?我们已经让灯先打开然后再关闭,所以它的最终状态应该是关闭的!问题是没有采取任何措施防止 LightOff 作业与 LightOn 作业同时运行。因此,即使首先调度了“打开”作业,但是只要它们同时执行,就无法预测这两个同时执行的作业的准确执行顺序。如果 LightOff 作业在 LightOn 作业之前结束运行,则会获得此无效结果。我们需要采用某种方法来防止这两个作业同时运行,这就是调度规则要完成的任务。
可以通过创建充当互斥对象(也称为 二进制信号灯)的简单调度规则来修正此示例:
class Mutex implements ISchedulingRule { public boolean isConflicting(ISchedulingRule rule) { return rule == this; } public boolean contains(ISchedulingRule rule) { return rule == this; } }
然后,将此规则添加到前一示例中的两个灯开关作业中:
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); } ... } }
现在,当调度这两个灯开关作业时,作业基础结构将调用 isConflicting 方法来比较这两个作业的调度规则。它将注意到这两个作业具有冲突的调度规则,并将确保它们按正确的顺序运行。它还将确保这两个作业从不同时运行。现在,即使您无数次运行该示例程序,始终都将获得相同的结果:
Turning the light on Turning the light off The light is on? false
还可以将规则用作常规锁定机制独立于作业。以下示例将获取 try/finally 块中的规则,在调用 beginRule 与调用 endRule 之间的这段时间内防止其它线程和作业使用该规则运行。
IJobManager manager = Platform.getJobManager(); try { manager.beginRule(rule, monitor); ... do some work ... } finally { manager.endRule(rule); }
当使用这种编码模式来获取和释放调度规则时,应该特别小心。如果未能结束已经对其调用了 beginRule 的规则,将永久锁定该规则。
尽管作业 API 定义了调度规则的合同,但是它实际上并未提供任何调度规则实现。实质上,一般的基础结构没有办法知道哪些作业集可以同时运行。缺省情况下,作业没有调度规则,执行已调度作业的速度与可以创建线程来运行它的速度一样快。
当作业确实具有调度规则时,isConflicting 方法用来确定规则是否与当前正在运行的任何作业的规则发生冲突。这样,isConflicting 的实现可以准确定义何时执行作业是安全的。在我们的灯开关示例中,isConflicting 实现只是将标识比较与所提供的规则配合使用。如果另一个作业具有完全相同的规则,则它们将不会同时运行。当编写您自己的调度规则时,一定要仔细阅读并遵循 isConflicting 的 API 合同。
如果作业具有几个不相关的约束,则可以使用 MultiRule 将多个调度规则组合起来。例如,如果作业需要打开灯开关,并且还要将信息写入网络套接字,则它可以使用工厂方法 MultiRule.combine 将用于灯开关的规则与用于套接字的写访问权的规则组合成一条规则。
我们已讨论有关 ISchedulingRule 的 isConflicting 方法,但是还远未提到 contains 方法。此方法用于许多客户机将不需要的调度规则的相当特殊的应用程序。调度规则可以按逻辑组成层次结构,用来控制对自然分层的资源的访问权。用来说明此概念的最简单示例是基于树结构的文件系统。如果应用程序想获得对目录的互斥锁定,则通常意味着它还想要对该目录中的文件和子目录的互斥访问权。contains 方法用来指定锁定之间的分层关系。如果不需要创建锁定的层次结构,则可以实现 contains 方法以便只调用 isConflicting。
下面是用于控制对 java.io.File 句柄的写访问权的分层锁定的示例。
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); } }
如果线程在已经拥有一个规则的情况下试图获取第二个规则,则可以使用 contains 方法。为了避免可能发生的死锁,任何给定的线程在任何给定的时间都只能拥有一个调度规则。如果线程在已经拥有一个规则的情况下调用 beginRule,则无论是通过前一次对 beginRule 的调用还是通过使用调度规则执行作业,都会使用 contains 方法查明这两个规则是否等效。如果已拥有的规则的 contains 方法返回 true,则调用 beginRule 将成功。如果 contains 方法返回 false,则将发生错误。
为了更具体地说明此情况,假定线程在“c:\temp”目录中拥有示例 FileLock 规则。虽然它拥有此规则时,但是只允许它修改该目录子树中的文件。如果它试图修改不在“c:\temp”下面的其它目录中的文件,则它将失败。因此,调度规则是一个具体规范,用来说明允许或不允许作业或线程执行的操作。违反该规范将导致运行时异常。在并行文献资料中,此技术称为两阶段锁定。在两阶段锁定方案中,进程提前很长时间指定对于特定任务它将需要的所有锁定,然后不允许在操作期间获得更多锁定。两阶段锁定消除了挂起并等待情况,挂起并等待情况是循环等待死锁的先决条件。因此,只将调度规则用作锁定原语的系统不可能进入死锁。