2012-09-27 14 views
8

W mojej aplikacji internetowej mam usługę w tle. Ta usługa używa klasy Generator zawierającej klasę Engine i ExecutorService skonfigurowaną do używania wielu wątków i akceptującą GeneratorTasks.Tworzenie fasoli szparagowej zachowuje się jak instancje ThreadLocal dla ExecutorService

@Component 
public class Generator { 
    @Autowired 
    private Engine heavyEngine; 

    private ExecutorService exec = Executors.newFixedThreadPool(3); 

    //I actually pass the singleton instance Generator class into the task. 
    public void submitTask(TaskModel model, TaskCallback callback) { 
     this.exec.submit(new GeneratorTask(model, this, callback)); 
    } 
} 

@Component 
public class Engine { 
    public Engine() { 
     //time-consuming initialization code here 
    } 
} 

public class GeneratorTask implements Callable<String> { 
    public GeneratorTask(TaskModel m, Generator g, ReceiptCallback c) { 
     this.m = m; 
     this.generator = g; 
     this.c = c; 
    } 

    public String call() throws Exception { 
     //This actually calls the Engine class of the generator. 
     //Maybe I should have passed the Engine itself? 
     this.generator.runEngine(c); 
    } 
} 

Inicjowanie klasy Engine zajmuje dużo czasu, więc najlepiej zainicjować ją tylko raz na wątku. Nie mogę zrobić tego pojedynczego wystąpienia, ponieważ instancja nie może być współużytkowana w wielu wątkach (polega na przetwarzaniu sekwencyjnym). Ponowne użycie instancji po zakończeniu zadania przetwarzania jest całkiem w porządku.

Zastanawiam się nad zmienną private Engine heavyEngine zmienną ThreadLocal. Jednak jestem również nowy w Spring, więc zastanawiałem się, czy istnieje inny sposób na wstawienie zmiennych ThreadLocal przy użyciu adnotacji wiosennych. Przyjrzałem się zakresowi fasoli do zakresu request, ale nie jestem pewien, jak powinienem to zrobić, biorąc pod uwagę mój projekt.

Wszelkie wskazówki dotyczące ulepszenia mojego projektu będą mile widziane.

+0

Ponieważ 'Engine' jest autowired Zakładam, że masz oświadczył to jako fasola. Domyślnie fasolka Spring jest singleton, więc istnieje duża szansa, że ​​'Engine' jest już singletonem ... –

+0

Przepraszam, mogłem być niejasny. Mój problem polega na tym, że nie mogę uczynić silnika pojedynczym, ponieważ ze względu na jego logikę przetwarzania nie można uzyskać dostępu do wielu współbieżnych wątków. Bezpiecznie jest ponownie użyć Silnika po zakończeniu "pracy", dlatego zastanawiałem się nad stworzeniem instancji na wątek, który jej używa. –

+0

Rozumiem, mówiłem, że z twoim aktualnym kodem Engine jest już singletonem, nawet jeśli nie jest to, co chcesz, to jest to, co faktycznie robi twój kod. –

Odpowiedz

9

Po pierwsze zrezygnuj z ThreadLocal - w tej klasie jest coś strasznego. To, czego potrzebujesz, to po prostu łączenie obiektów. To nie jest dobrze znany funkcję, ale Wiosna obsługuje to za dobrze:

<bean id="engineProto" class="Engine" scope="prototype" lazy-init="true"/> 

<bean id="engine" class="org.springframework.aop.framework.ProxyFactoryBean"> 
    <property name="targetSource"> 
     <bean class="org.springframework.aop.target.CommonsPoolTargetSource"> 
      <property name="targetClass" value="Engine"/> 
      <property name="targetBeanName" value="engineProto"/> 
      <property name="maxSize" value="3"/> 
      <property name="maxWait" value="5000"/> 
     </bean> 
    </property> 
</bean> 

Teraz, kiedy wstrzykiwać engine, będziesz faktycznie otrzymać obiekt proxy (Engine będzie potrzebował interfejs), który będzie przekazywać wszystkie połączenia do swobodnego obiektu w basen. Rozmiar puli można konfigurować. Oczywiście nic nie stoi na przeszkodzie, aby użyć ThreadLocalTargetSource, która używa ThreadLocal zamiast Commons Pool. Oba podejścia gwarantują wyłączny, bezpieczny dla wątków dostęp do Engine.

Wreszcie można ręcznie połączyć pulę (ale piękno powyższego rozwiązania jest całkowicie przezroczyste) lub przejść do EJB, które są połączone z definicji.

+1

Wycieki pamięci Classloader są trochę przerażające. – Vedran

+0

Możesz więc wprowadzić fasolę "silnika" w dowolne miejsce, spodziewając się wystąpienia interfejsu silnika? Czy tworzy on po prostu anonimową klasę, która dziedziczy po interfejsie Engine, który wykorzystuje buforowanie za kulisami? – guitar80

1

Utworziłbym fabrykę dla Engine i zadzwoniłbym pod numer GeneratorTask. W ten sposób można usunąć pole heavyEngine wewnątrz Generator i argument konstruktora Generator w GeneratorTask.
Następnie, jeśli chcesz zapisać czas inicjalizacji Engine, możesz nadal zadeklarować go jako singleton, ale użyj słowa kluczowego synchronized w metodach bez wątków.

public class Generator {  
    @Autowired private EngineFactory engineFactory; 
    private ExecutorService exec = Executors.newFixedThreadPool(3); 

    public void submitTask(TaskModel model, TaskCallback callback) { 
     this.exec.submit(new GeneratorTask(engineFactory, model, callback)); 
    } 
} 

public class EngineFactory { 
    @Autowired private Engine instance; 

    public Engine getInstance() { 
     return instance; 
    } 
} 

public class Engine { 
    public Engine() { 
     //time-consuming initialization code here 
    } 

    public synchronized void runEngine() { 
     // Do non thread safe stuf 
    } 
} 

public class GeneratorTask implements Callable<String> { 
    public GeneratorTask(EngineFactory f, TaskModel m, ReceiptCallback c) { 
     this.f = f; 
     this.m = m; 
     this.c = c; 
    } 

    public String call() throws Exception { 
     Engine engine = f.getInstance(); 
     engine.runEngine(); 
     ... 
    } 
} 

Najprawdopodobniej jest to czysty sposób wiosenny, aby przekazać silnik do maszyny Callable, ale w tym przypadku fabryka jest wystarczająco dobra, moim zdaniem.

5

FYI, Spring 3.0 i nowsze wersje zawierają implementację Scope-backed, SimpleThreadScope.

Aby z niego skorzystać należy zarejestrować zakres niestandardowe:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> 
    <property name="scopes"> 
     <map> 
      <entry key="thread"> 
       <bean class="org.springframework.context.support.SimpleThreadScope" /> 
      </entry> 
     </map> 
    </property> 
</bean> 

A potem zadeklarować gwint-scoped Fasola:

<bean id="myBean" class="com.foo.MyBean" scope="thread"> 
    ... 
</bean> 
Powiązane problemy