Eclips运行时概述4

1.1.1        调度规则

作业调度规则可以用来控制作业的运行何时与其它作业有关。特别是,调度规则使您可以防止多个作业在并发性可能导致不一致结果的情况下同时运行。它们还使您可以保证一系列作业的执行顺序。通过一个示例很好地说明了调度规则的作用。让我们从定义用来同时将灯打开和关闭的两个作业开始:

   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的规则,将永久锁定该规则。

1.1.2      建立您自己的规则

尽管作业 API 定义了调度规则的合同,但是它实际上并未提供任何调度规则实现。实质上,一般的基础结构没有办法知道哪些作业集可以同时运行。缺省情况下,作业没有调度规则,执行已调度作业的速度与可以创建线程来运行它的速度一样快。

当作业确实具有调度规则时,isConflicting方法用来确定规则是否与当前正在运行的任何作业的规则发生冲突。这样,isConflicting的实现可以准确定义何时执行作业是安全的。在我们的灯开关示例中,isConflicting实现只是将标识比较与所提供的规则配合使用。如果另一个作业具有完全相同的规则,则它们将不会同时运行。当编写您自己的调度规则时,一定要仔细阅读并遵循isConflicting API 合同。

如果作业具有几个不相关的约束,则可以使用 MultiRule 将多个调度规则组合起来。例如,如果作业需要打开灯开关,并且还要将信息写入网络套接字,则它可以使用工厂方法MultiRule.combine将用于灯开关的规则与用于套接字的写访问权的规则组合成一条规则。

1.1.3        规则层次结构

我们已讨论有关 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”下面的其它目录中的文件,则它将失败。因此,调度规则是一个具体规范,用来说明允许或不允许作业或线程执行的操作。违反该规范将导致运行时异常。在并发性文献资料中,此技术称为两阶段锁定。在两阶段锁定方案中,进程提前很长时间指定对于特定任务它将需要的所有锁定,然后不允许在操作期间获得更多锁定。两阶段锁定消除了挂起并等待情况,挂起并等待情况是循环等待死锁的先决条件。因此,只将调度规则用作锁定原语的系统不可能进入死锁。

1.1.4        锁定

系统中的多个作业有可能需要访问和处理同一个对象。ILock定义用于授予对共享对象的互斥访问的协议。当作业需要访问共享对象时,它会获取对该对象的锁定。当它处理完该对象时,它就会释放该锁定。

锁定通常是在插件创建或者第一次访问共享对象时创建的。即,引用共享对象的代码也会引用对共享对象的锁定。我们将从创建 myLock 锁定开始,将使用该锁定来控制对 myObject 的访问权:

   ...
   myObject = initializeImportantObject();
   IJobManager jobMan = Platform.getJobManager();
   myLock = jobMan.newLock();
   ...

平台提供了 ILock 的健壮实现。作业管理器提供了此锁定的实例供客户机使用。这些锁定都相互了解,并且可以避免循环死锁。(我们将立即对此进行更详细的说明。)

每当作业中的代码需要访问 myObject 时,它就必须首先获得对该对象的锁定。以下片段说明使用锁定的常见习惯用语:

...
// I need to manipulate myObject, so I get its lock first.
try {
        myLock.acquire();
        updateState(myObject);  // manipulate the object
        } finally {
        lock.release();
}
...

在可以对调用作业授予对锁定的互斥访问之前,将不会返回 acquire() 方法。换句话说,如果其它某个作业已经获得锁定,则此代码将被阻塞,直到锁定可用为止。注意,获得锁定并处理 myObject 的代码包括在try块中,因此,如果在使用对象时发生任何异常,则可以释放该锁定。

看起来很简单,对吗?幸好,锁定使用起来相当简单。它们还是可重入的,这意味着不必担心作业多次获得同一锁定。每个锁定都会对获得和释放特定线程的次数进行计数,并且仅当释放次数等于获得次数时才会从作业中释放锁定。

死锁

我们前面提到了作业管理器提供的锁定之间是相互了解的,并且可以避免循环死锁。要了解死锁是如何发生的,让我们查看一个简单的方案。假定作业 A”获得锁定 A”,随后尝试获得锁定 B”。同时,锁定 B”作业 B”挂起,而作业 B”现在已被阻塞,它正在等待锁定 A”。这种死锁表示在作业之间使用锁定时存在底层设计问题。虽然这种简单情况很容易避免,但是,随着设计中使用的作业数和锁定数逐渐增加,意外产生死锁的机会也将增大。

幸好,平台将帮助您发现死锁。当作业管理器检测到死锁情况时,它就会将诊断信息显示在日志中,并且会描述死锁情况。然后,它通过以下方法来解除死锁:将对被阻塞的作业拥有的锁定的访问权临时授予给正在等待这些锁定的其它作业。仔细测试涉及到多个锁定的任何实现并修正平台报告的任何死锁情况是很重要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值