środa, 1 lutego 2012

EIwS-Cert - współbieżność w Springu (cz. 2)

W kolejnym z cyklu (Współbieżność w Springu cz. 1) wpisie dotyczącym współbieżności w Springu przedstawiona zostanie koncepcja planowania zadań.

Podstawowym interfejsem Spring'a realizującym tę funkcjonalność jest TaskScheduler. Poniżej przestawiona jest hierarchia wymienionego interfejsu.
Hierarchia interfejsu TaskScheduler
TaskScheduler deklaruje poniższe metody:
  • schedule(Runnable, Date) - podane zadanie (Runnable) uruchamiane jest w określonej dacie. Jeżeli podana data jest przeszła to zadanie zostanie niezwłocznie zlecone do uruchomienia.
  • scheduleAtFixedRate(Runnable, long) - zlecane zadanie będzie uruchamiane cyklicznie z cyklem pomiędzy kolejnymi uruchomieniami określonym w milisekundach, a pierwsze uruchomienie odbędzie się najszybciej jak to możliwe.
  • scheduleAtFixedRate(Runnable, Date, long) - zlecane zadanie będzie uruchamiane cyklicznie  począwszy od określonej daty, cykl pomiędzy kolejnymi uruchomieniami określony jest w milisekundach.
  • scheduleAtFixedDelay(Runnable, long) - zlecane zadanie będzie uruchamiane cyklicznie, z określonym cyklem w milisekundach pomiędzy końcem przetwarzania poprzedniego zadania i początkiem następnego. Pierwsze wykonanie odbędzie się najszybciej jak to możliwe.
  • scheduleAtFixedDelay(Runnable, Date, long) - zlecane zadanie będzie uruchamiane cyklicznie, z określonym cyklem w milisekundach pomiędzy końcem jednego wykonania i początkiem następnego. Pierwsze wykonanie zostanie wywołane o podanej dacie.
  • schedule(Runnable, Trigger) - zlecane zadanie będzie uruchamiane zgodnie z definicją - Trigger.
Każda z powyższych metod zwraca uchwyt do przyszłego wyniku realizacji zleconego zadania: ScheduledFuture.

Spring zapewnia 3 implementacje powyższego interfejsu:
  1. ConcurrentTaskScheduler - jest adapterem dla ScheduledExecutorService z Javy.
  2. ThreadPoolTaskScheduler - opakowuje natywny ScheduledThreadPool, wystawiając jako własne właściwości parametry Javowej implementacji.
  3. TimerManagerTaskScheduler - implementacja opakowująca TimerManager'a z commonj.
Wróćmy jeszcze do ostatniej metody interfejsu TaskScheduler. Jest ona szczególnie interesująca, ponieważ pozwala jej klientom elastycznie określać czas uruchamiania zadań - osiągnięto to dzięki interfejsowi Trigger
TriggerContext jest również interfejsem. Pozwala on otrzymać informacje o tym kiedy ostatni wykonanie zadania zostało zakończone, kiedy zostało zlecone i na kiedy było zaplanowane.
Warto zwrócić uwagę na jedną implementację Trigger'a dostarczaną ze Spring'iem - CronTrigger. Implementacja ta pozwala określać czas wykonania zleconego do realizacji zadania za pomocą wyrażeń Cron.

Poniżej przedstawiono przykład wykorzystania implementacji ThreadPoolTaskScheduler.

   1:  package pl.com.mbsoftware.eiws.concurrency;
   2:   
   3:  import org.springframework.context.annotation.Bean;
   4:  import org.springframework.context.annotation.Configuration;
   5:  import org.springframework.scheduling.TaskScheduler;
   6:  import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
   7:   
   8:  @Configuration
   9:  public class TaskSchedulerTestSpringConfiguration {
  10:   
  11:      @Bean
  12:      public TaskScheduler taskScheduler() {
  13:          return new ThreadPoolTaskScheduler();
  14:      }
  15:   
  16:  }
Powyżej konfiguracja spring'owa wykorzystana w testach jednostkowych - TaskSchedulerTestSpringConfiguration. Dostarcza ona jednego bean'a realizującego interfejs TaskScheduler.


   1:  package pl.com.mbsoftware.eiws.concurrency;
   2:   
   3:  import org.junit.Test;
   4:  import org.junit.runner.RunWith;
   5:  import org.slf4j.Logger;
   6:  import org.slf4j.LoggerFactory;
   7:  import org.springframework.beans.factory.annotation.Autowired;
   8:  import org.springframework.scheduling.TaskScheduler;
   9:  import org.springframework.scheduling.support.CronTrigger;
  10:  import org.springframework.test.context.ContextConfiguration;
  11:  import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  12:   
  13:  @RunWith(SpringJUnit4ClassRunner.class)
  14:  @ContextConfiguration(classes = { TaskSchedulerTestSpringConfiguration.class })
  15:  public class TaskSchedulerTest {
  16:   
  17:      private final static Logger log = LoggerFactory.getLogger(TaskSchedulerTest.class);
  18:   
  19:      @Autowired
  20:      private TaskScheduler taskScheduler;
  21:   
  22:      @Test
  23:      public void testScheduleWithTriggerEverySecond() throws InterruptedException {
  24:          taskScheduler.schedule(new Runnable() {
  25:              
  26:              @Override
  27:              public void run() {
  28:                  log.debug("Run by thread: [{}]", Thread.currentThread().getName());
  29:              }
  30:          }, new CronTrigger("* * * * * *"));
  31:          Thread.sleep(5000);
  32:      }
  33:   
  34:  }
Powyżej z kolei test jednostkowy (TaskSchedulerTest) sprawdzający działanie dostarczanego TaskScheduler'a. W tym wypadku testowana jest metoda schedule(Runnable, Trigger). Za pomocą wyrażenia Cron "* * * * * *" definiowane jest czas odpalania zadania - będzie ono uruchamiane co 1 sekundę.

Poniżej przykład wykonania testu. Jak widać zadanie uruchamiane jest 5 razy co sekundę - na 5 sekund po zleceniu zadań wstrzymywany jest wątek główny.

0    [taskScheduler-1] DEBUG pl.com.mbsoftware.eiws.concurrency.TaskSchedulerTest  - Run by thread: [taskScheduler-1]
1000 [taskScheduler-1] DEBUG pl.com.mbsoftware.eiws.concurrency.TaskSchedulerTest  - Run by thread: [taskScheduler-1]
2000 [taskScheduler-1] DEBUG pl.com.mbsoftware.eiws.concurrency.TaskSchedulerTest  - Run by thread: [taskScheduler-1]
3000 [taskScheduler-1] DEBUG pl.com.mbsoftware.eiws.concurrency.TaskSchedulerTest  - Run by thread: [taskScheduler-1]
4000 [taskScheduler-1] DEBUG pl.com.mbsoftware.eiws.concurrency.TaskSchedulerTest  - Run by thread: [taskScheduler-1]

Na koniec propozycja porównania możliwości planowania zadań, które daje nam Java z tymi udostępnianymi przez Spring.
Dla przypomnienia - podstawowym interfejsem w Javie (jeśli chodzi o planowanie zadań) jest ScheduledExecutorService, natomiast w Spring'u jest, omawiany w bieżącym wpisie, TaskScheduler.



FunkcjonalnośćJavaSpring
Zaplanowanie wykonania zadania na konkretny czas++
Zaplanowanie wykonania zadania za określony czas++
Wykonywanie zadania cyklicznie co określony czas pomiędzy uruchomieniami kolejnych wywołań++
Wykonywanie zadania cyklicznie co określony czas pomiędzy koniem przetwarzania poprzedniego zadania a początkiem przetwarzania następnego++
Możliwość zdefiniowania własnej strategii uruchamiania kolejnych wywołań zleconego zadania-+
Ukrycie implementacji dostarczanych przez zewnętrzne providery pod spójnym interfejsem-+


Pomimo tego, że Spring korzysta z natywnych elementów dostarczanych z Javą, udostępnia o wiele więcej funkcjonalności niż te standardy. Poza tym dzięki temu, że dostarcza spójny interfejs do różnych implementacji pozwala uzyskać o wiele większą elastyczność niż podstawowe implementacje. 

Brak komentarzy:

Prześlij komentarz