Wie viele Kerne dürfen es bei CDI werden?

CDI – Java SE, die Welt der Threads

Sven Ruppert
©Shutterstock.com/maximmmmum

Was auf Java-EE-Seite nicht gestattet, ist auf der Java-SE-Seite zu wenig verwendet. Die Rede ist von den guten alte Threads. Einiges hat sich seit den letzten Versionen von Java getan, aber wie funktioniert das zusammen mit CDI?

Bisher wurden in unseren Beispielen der Serie immer nur die Aspekte ohne die Problematik der Nebenläufigkeit betrachtet. Das ist aber nur die halbe Realität. Die Anzahl der Kerne pro Prozessor steigt von Generation zu Generation, diese zu verwenden ist also immer ratsamer.

Der Klassiker: SimpleDateFormat

Beginnen wir mit einem einfachen Fall, in dem wir eine fachliche Logik (Listing 1) in einer Menge von n Threads verwenden. Die Implementierung selbst ist als Singleton realisiert. Hier in diesem Beispiel wurde mit Absicht das SimpleDateFormat verwendet, da es nicht ThreadSave ist.  

@Singleton
public class DemoLogic {
  @Inject
  @CDISimpleDateFormatter(value = "rechnungsdatum")
  Instance <SimpleDateFormat> sdf;

  public String doIt(final Date date){
    final SimpleDateFormat simpleDateFormat = sdf.get();
    final String format1 = simpleDateFormat.format(date);
    final Date parse;
    try {
      parse = simpleDateFormat.parse(format1);
      final String format2 = simpleDateFormat.format(parse);
      return format2;
    } catch (ParseException e) {
      e.printStackTrace();
    }
    return "noooop...";
  }
}

Die Implementierung der Methode doIt() ist so gewählt, dass es auf jeden Fall zu Komplikationen kommt. Unter [1] ist der Quelltext zu finden. Die Klasse ContextResolverTest enthält die jUnit Tests die das Problem demonstrieren. Die Tests test001 bis test005 laufen ohne Probleme durch, da nur ein Thread verwendet wird. Ab test006 (Listing 2) allerdings werden n-Threads verwendet und schon beginnen die Probleme.

@Inject DemoLogic demoLogic;
@Test
public void test006() throws Exception {
  final String format = demoLogic.doIt(DATE);
  Assert.assertEquals(format, DATE_STRING);

  final List<Thread> threads = new ArrayList<>();
  for (int i = 0; i < MAX_ROUNDS; i++) {
    final Thread thread = new Thread(() ->
      Assert.assertEquals(demoLogic.doIt(DATE), 
      DATE_STRING));
    threads.add(thread);
  }
  for (final Thread thread : threads) {
    thread.start();
  }
  for (final Thread thread : threads) {
    thread.join();
  }
}

Wie kann man das nun lösen? Eine Methode ist sicherlich, einen Wrapper um die Klasse SimpleDateFormat zu schreiben, der die Methodenaufrufe serialisiert (Listing 3). Ob das aus Performancegründen immer sinnvoll ist mag ich bezweifeln. Wie also kann man die Teile die nicht ThreadSave sind isolieren?

@Singleton
public class ThreadSafeSimpleDateFormat  {
  private Semaphore semaphore = new Semaphore(1);
  @Inject
  @CDISimpleDateFormatter("rechnungsdatum")
  private SimpleDateFormat sdf;

  public String format(Date date) {
    try {
      semaphore.acquire();
      final String format = sdf.format(date);
      semaphore.release();
      return format;
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return "noooop.....";
  }

  public Date parse(String text) throws ParseException {
    try {
      semaphore.acquire();
      final Date parse = sdf.parse(text);
      semaphore.release();
      return parse;
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return null;
  }
}

Die Lösung besteht darin, das SimpleDateFormat jeweils in den ThreadLocal zu legen. Der übliche Weg geht über die Definition wie in Listing 4. Es gibt jedoch eine elegantere Methode.

private static final ThreadLocal<SimpleDateFormat> 
        formatter = new ThreadLocal<SimpleDateFormat>() {
  @Override
  protected SimpleDateFormat initialValue() {
    return new SimpleDateFormat("yyyy.MM.dd");
  }
};

public String formatIt(Date date) {
  return formatter.get().format(date);
}

Die Implementierung Weld gibt einem hierfür die Annotation ThreadScoped. Das Prinzip dahinter ist einfach. Es wird ein RunnableDecorator (Listing 5) definiert, der sich um alle Runnable.run() Methodenaufrufe legt. Aktivieren kann dieses in der beans.xml (Listing 6).

@Decorator
public class RunnableDecorator implements Runnable {

  @Inject @Delegate Runnable runnable;
  public void run() {
    // set up context for this thread
    final ThreadContext threadContext = 
      WeldSEBeanRegistrant.THREAD_CONTEXT;
    try {
      threadContext.activate();
      // run the original thread
      runnable.run();
    } finally {
      threadContext.invalidate();
      threadContext.deactivate();
    }
  }
}

 

<beans>
  <decorators>
    <class>
      org.jboss.weld.environment.se.threading.RunnableDecorator
    </class>
  </decorators>
</beans>

Die Verwendung der Annotation ist recht einfach und in Listing 7 dargestellt. Wichtig an der Stelle ist, dass erst in dem Methodenaufruf von dem Attribut der Klasse Instance<SimpleDateFormat> per get() eine Instanz geholt wird. Damit wird genau diese Instanz in den ThreadLocal des aufrufenden Thread gelegt.  

@Singleton
public class DemoLogicThreadScoped {
  @Inject
  @ThreadScoped
  @CDISimpleDateFormatter(value = "rechnungsdatum")
  Instance <SimpleDateFormat> sdf;

  public String doIt(final Date date){
    // in ThreadLocal gelegt
    final SimpleDateFormat simpleDateFormat = sdf.get();
    final String format1 = simpleDateFormat.format(date);
    final Date parse;
    try {
      parse = simpleDateFormat.parse(format1);
      final String format2 =
        simpleDateFormat.format(parse);
      return format2;
    } catch (ParseException e) {
      e.printStackTrace();
    }
    return "noooop...";
  }
}

Fazit

Die Beispiele zeigen, wie einfach man mittels CDI Teile, die nicht ThreadSave sind, in den ThreadLocal legen kann. Die Verwendung dieser Annotation ist allerdings ausschließlich in dem Modul SE von Weld enthalten. Konsequent durchgeführt, kann der Einsatz von CDI bei diesen Stellen zu starken Codereduktionen führen.

Die Quelltexte zu diesem Text sind unter [1] zu finden. Wer umfangreichere Beispiele zu diesem Thema sehen möchte, dem empfehle einen Blick auf [2] (Modul cdi-commons).

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.