스케줄링 규칙

작업 스케줄링 규칙은 작업이 다른 작업과 관련하여 실행되는 시기를 제어하는 데 사용될 수 있습니다. 특히 스케줄링 규칙을 사용하면 동시성의 결과가 일관되지 않을 수 있는 상황에서 다중 작업이 동시에 실행되지 않게 할 수 있습니다. 또한 이 규칙을 사용하면 일련의 작업에 대한 실행 순서를 보장할 수 있습니다. 스케줄링 규칙의 용도는 예제를 통해 가장 잘 설명됩니다. 다음과 같이 표시등의 스위치를 켜고 끄는 데 사용되는 두 가지 작업을 정의하여 설명을 시작합니다.

      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과 동시에 실행되지 못하도록 막을 수 없다는 것입니다. 따라서 "켜기" 작업이 먼저 스케줄되었더라도 동시 실행이란 두 가지 동시 작업에 대한 정확한 실행 순서를 예측할 방법이 없음을 의미합니다. LightOn 작업 전에 LightOff 작업이 실행을 종료할 경우 이와 같이 올바르지 않은 결과를 얻게 됩니다. 필요한 것은 두 가지 작업이 동시에 실행되지 않도록 하는 방법이며, 이것이 스케줄링 규칙이 어디에서 오는지에 대한 이유입니다.

mutex(2진 세마포어라고도 함) 역할을 하는 단순 스케줄링 규칙을 작성하여 이 예제를 다음과 같이 수정할 수 있습니다.

      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 블록에서 규칙을 확보하므로, 기타 스레드 및 작업이 beginRuleendRule의 호출 간 지속기간 동안 해당 규칙을 사용하여 실행되지 못하도록 합니다.

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

해당 코딩 패턴을 사용하여 스케줄링 규칙을 확보하고 해제할 때 특별한 주의를 기울여야 합니다. beginRule을 호출한 규칙을 종료하는 데 실패할 경우 해당 규칙을 영원히 잠그게 됩니다.

고유 규칙 작성

작업 API가 스케줄링 규칙에 대한 계약을 정의하더라도 실제로는 스케줄링 규칙 구현을 제공하지 않습니다. 일반 하부구조에는 어떤 작업 세트를 동시에 실행해도 괜찮은지를 아는 방법이 없습니다. 기본값으로 작업에는 스케줄링 규칙이 없으므로, 스케줄된 작업은 스레드가 이 작업을 실행하도록 작성되는 즉시 실행됩니다.

작업에 스케줄링 규칙이 있으면 isConflicting 메소드는 규칙이 현재 실행 중인 작업의 규칙과 충돌하는지 여부를 판별하는 데 사용됩니다. 따라서 isConflicting 구현은 작업 실행이 안전한 시기를 정확히 정의할 수 있습니다. 표시등 스위치 예제에서 isConflicting 구현은 단순히 제공된 규칙과의 ID 비교를 사용합니다. 다른 작업에 동일한 규칙이 있을 경우 작업은 동시에 실행될 수 없습니다. 고유 스케줄링 규칙을 쓸 때 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" 아래에 있지 않는 기타 디렉토리의 파일을 수정하려고 시도할 경우 실패합니다. 따라서 스케줄링 규칙은 작업 또는 스레드가 수정하도록 허용되거나 허용되지 않은 항목에 대한 구체적인 스펙입니다. 해당 스펙을 위반하면 런타임 예외가 발생합니다. 동시성 측면에서 이 기술을 2단계 잠금이라고 합니다. 2단계 잠금 설계에서 프로세스는 대개 특정 타스크에 필요한 모든 잠금을 미리 지정하므로, 그 이후에는 조작 중 추가 잠금을 확보하도록 허용되지 않습니다. 2단계 잠금은 순환 대기 교착 상태의 필수조건인 유지 및 대기(hold-and-wait) 조건을 제거합니다. 따라서 스케줄링 규칙을 기본 잠금으로만 사용하는 시스템이 교착 상태에 빠지는 것은 불가능합니다.