Статьи

Услуги Spring Thread Pool

Пулы потоков очень важны для выполнения синхронных и асинхронных процессов. В этой статье показано, как разрабатывать и отслеживать службы пула потоков с помощью Spring. Создание пула потоков было объяснено двумя альтернативными методами.

Используемые технологии :

JDK 1.6.0_21
Spring 3.0.5
Maven 3.0.2

ШАГ 1: СОЗДАТЬ MAVEN ПРОЕКТ

Maven проект создается как показано ниже. (Его можно создать с помощью Maven или IDE Plug-in).

ШАГ 2: БИБЛИОТЕКИ

Spring-зависимости добавляются в pom.xml Maven.

01
02
03
04
05
06
07
08
09
10
11
<!-- Spring 3 dependencies -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

Для создания runnable-jar, ниже плагин может быть использован.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>1.3.1</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer
                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.otv.exe.Application</mainClass>
                    </transformer>
                    <transformer
                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.handlers</resource>
                    </transformer>
                    <transformer
                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.schemas</resource>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

ШАГ 3: СОЗДАЙТЕ КЛАСС ЗАДАЧИ

Новый класс TestTask создается путем реализации Runnable Interface. Этот класс показывает выполняемые задачи.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.otv.task;
  
import org.apache.log4j.Logger;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class TestTask implements Runnable {
  
    private static Logger log = Logger.getLogger(TestTask.class);
    String taskName;
  
    public TestTask() {
    }
  
    public TestTask(String taskName) {
        this.taskName = taskName;
    }
  
    public void run() {
        try {
            log.debug(this.taskName + " : is started.");
            Thread.sleep(10000);
            log.debug(this.taskName + " : is completed.");
        } catch (InterruptedException e) {
            log.error(this.taskName + " : is not completed!");
            e.printStackTrace();
        }
    }
  
    @Override
    public String toString() {
        return (getTaskName());
    }
  
    public String getTaskName() {
        return taskName;
    }
  
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }
}

ШАГ 4: СОЗДАЙТЕ TestRejectedExecutionHandler CLASS

Класс TestRejectedExecutionHandler создается путем реализации интерфейса RejectedExecutionHandler. Если нет свободного потока и переполнения очереди, задачи будут отклонены. Этот класс обрабатывает отклоненные задачи.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package com.otv.handler;
  
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
  
import org.apache.log4j.Logger;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class TestRejectedExecutionHandler implements RejectedExecutionHandler {
  
    private static Logger log = Logger.getLogger(TestRejectedExecutionHandler.class);
  
    public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
        log.debug(runnable.toString() + " : has been rejected");
    }
}

ШАГ 5: СОЗДАЙТЕ ITestThreadPoolExecutorService INTERFACE

ITestThreadPoolExecutorService Интерфейс создан.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.otv.srv;
  
import java.util.concurrent.ThreadPoolExecutor;
  
import com.otv.handler.TestRejectedExecutionHandler;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public interface ITestThreadPoolExecutorService {
  
    public ThreadPoolExecutor createNewThreadPool();
  
    public int getCorePoolSize();
  
    public void setCorePoolSize(int corePoolSize);
  
    public int getMaxPoolSize();
  
    public void setMaxPoolSize(int maximumPoolSize);
  
    public long getKeepAliveTime();
  
    public void setKeepAliveTime(long keepAliveTime);
  
    public int getQueueCapacity();
  
    public void setQueueCapacity(int queueCapacity);
  
    public TestRejectedExecutionHandler getTestRejectedExecutionHandler();
  
    public void setTestRejectedExecutionHandler(TestRejectedExecutionHandler testRejectedExecutionHandler);
  
}

ШАГ 6: СОЗДАЙТЕ TestThreadPoolExecutorService CLASS

Класс TestThreadPoolExecutorService создается путем реализации интерфейса ITestThreadPoolExecutorService. Этот класс создает новый пул потоков.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.otv.srv;
  
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
  
import com.otv.handler.TestRejectedExecutionHandler;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class TestThreadPoolExecutorService implements ITestThreadPoolExecutorService {
  
    private int  corePoolSize;
    private int  maxPoolSize;
    private long keepAliveTime;
    private int  queueCapacity;
    TestRejectedExecutionHandler testRejectedExecutionHandler;
  
    public ThreadPoolExecutor createNewThreadPool() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(getCorePoolSize(),
                                                                getMaxPoolSize(),
                                                                getKeepAliveTime(),
                                                                TimeUnit.SECONDS,
                                                                new ArrayBlockingQueue(getQueueCapacity()),
                                                                getTestRejectedExecutionHandler());
        return executor;
    }
  
    public int getCorePoolSize() {
        return corePoolSize;
    }
  
    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }
  
    public int getMaxPoolSize() {
        return maxPoolSize;
    }
  
    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }
  
    public long getKeepAliveTime() {
        return keepAliveTime;
    }
  
    public void setKeepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
    }
  
    public int getQueueCapacity() {
        return queueCapacity;
    }
  
    public void setQueueCapacity(int queueCapacity) {
        this.queueCapacity = queueCapacity;
    }
  
    public TestRejectedExecutionHandler getTestRejectedExecutionHandler() {
        return testRejectedExecutionHandler;
    }
  
    public void setTestRejectedExecutionHandler(TestRejectedExecutionHandler testRejectedExecutionHandler) {
        this.testRejectedExecutionHandler = testRejectedExecutionHandler;
    }
}

ШАГ 7: СОЗДАЙТЕ ИНТЕРФЕЙС IThreadPoolMonitorService

IThreadPoolMonitorService Интерфейс создан.

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.otv.monitor.srv;
  
import java.util.concurrent.ThreadPoolExecutor;
  
public interface IThreadPoolMonitorService extends Runnable {
  
    public void monitorThreadPool();
  
    public ThreadPoolExecutor getExecutor();
  
    public void setExecutor(ThreadPoolExecutor executor);
  
}

ШАГ 8: СОЗДАНИЕ ThreadPoolMonitorService CLASS

Класс ThreadPoolMonitorService создается путем реализации интерфейса IThreadPoolMonitorService. Этот класс контролирует созданный пул потоков.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.otv.monitor.srv;
  
import java.util.concurrent.ThreadPoolExecutor;
import org.apache.log4j.Logger;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class ThreadPoolMonitorService implements IThreadPoolMonitorService {
  
    private static Logger log = Logger.getLogger(ThreadPoolMonitorService.class);
    ThreadPoolExecutor executor;
    private long monitoringPeriod;
  
    public void run() {
        try {
            while (true){
                monitorThreadPool();
                Thread.sleep(monitoringPeriod*1000);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
  
    public void monitorThreadPool() {
        StringBuffer strBuff = new StringBuffer();
        strBuff.append("CurrentPoolSize : ").append(executor.getPoolSize());
        strBuff.append(" - CorePoolSize : ").append(executor.getCorePoolSize());
        strBuff.append(" - MaximumPoolSize : ").append(executor.getMaximumPoolSize());
        strBuff.append(" - ActiveTaskCount : ").append(executor.getActiveCount());
        strBuff.append(" - CompletedTaskCount : ").append(executor.getCompletedTaskCount());
        strBuff.append(" - TotalTaskCount : ").append(executor.getTaskCount());
        strBuff.append(" - isTerminated : ").append(executor.isTerminated());
  
        log.debug(strBuff.toString());
    }
  
    public ThreadPoolExecutor getExecutor() {
        return executor;
    }
  
    public void setExecutor(ThreadPoolExecutor executor) {
        this.executor = executor;
    }  
  
    public long getMonitoringPeriod() {
        return monitoringPeriod;
    }
  
    public void setMonitoringPeriod(long monitoringPeriod) {
        this.monitoringPeriod = monitoringPeriod;
    }
}

ШАГ 9: СОЗДАЙТЕ СТАРТ КЛАСС

Стартовый класс создан.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.otv.start;
  
import java.util.concurrent.ThreadPoolExecutor;
  
import org.apache.log4j.Logger;
  
import com.otv.handler.TestRejectedExecutionHandler;
import com.otv.monitor.srv.IThreadPoolMonitorService;
import com.otv.monitor.srv.ThreadPoolMonitorService;
import com.otv.srv.ITestThreadPoolExecutorService;
import com.otv.srv.TestThreadPoolExecutorService;
import com.otv.task.TestTask;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class Starter {
  
    private static Logger log = Logger.getLogger(TestRejectedExecutionHandler.class);
  
    IThreadPoolMonitorService threadPoolMonitorService;
    ITestThreadPoolExecutorService testThreadPoolExecutorService;
  
    public void start() {
  
        // A new thread pool is created...
        ThreadPoolExecutor executor = testThreadPoolExecutorService.createNewThreadPool();
        executor.allowCoreThreadTimeOut(true);
  
        // Created executor is set to ThreadPoolMonitorService...
        threadPoolMonitorService.setExecutor(executor);
  
        // ThreadPoolMonitorService is started...
        Thread monitor = new Thread(threadPoolMonitorService);
        monitor.start();
  
        // New tasks are executed...
        for(int i=1;i<10;i++) {
            executor.execute(new TestTask("Task"+i));
        }
  
        try {
            Thread.sleep(40000);
        } catch (Exception e)   {
            log.error(e.getMessage());
        }
  
        for(int i=10;i<19;i++) {
            executor.execute(new TestTask("Task"+i));
        }
  
        // executor is shutdown...
        executor.shutdown();
    }  
  
    public IThreadPoolMonitorService getThreadPoolMonitorService() {
        return threadPoolMonitorService;
    }
  
    public void setThreadPoolMonitorService(IThreadPoolMonitorService threadPoolMonitorService) {
        this.threadPoolMonitorService = threadPoolMonitorService;
    }
  
    public ITestThreadPoolExecutorService getTestThreadPoolExecutorService() {
        return testThreadPoolExecutorService;
    }
  
    public void setTestThreadPoolExecutorService(ITestThreadPoolExecutorService testThreadPoolExecutorService) {
        this.testThreadPoolExecutorService = testThreadPoolExecutorService;
    }
}

ШАГ 10: СОЗДАЙТЕ КЛАСС ПРИЛОЖЕНИЙ

Класс приложения создан. Этот класс запускает приложение.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.otv.start;
  
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
  
/**
 * @author onlinetechvision.com
 * @since 17 Oct 2011
 * @version 1.0.0
 *
 */
public class Application {
  
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       Starter starter = (Starter) context.getBean("Starter");
       starter.start();
    }
  
}

ШАГ 11: СОЗДАТЬ applicationContext.xml

applicationContext.xml создан.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  
  
    <!-- Beans Declaration -->
    <bean id="TestTask" class="com.otv.task.TestTask"></bean>
    <bean id="ThreadPoolMonitorService" class="com.otv.monitor.srv.ThreadPoolMonitorService">
        <property name="monitoringPeriod"  value="5" />
    </bean>
    <bean id="TestRejectedExecutionHandler" class="com.otv.handler.TestRejectedExecutionHandler"></bean>
    <bean id="TestThreadPoolExecutorService" class="com.otv.srv.TestThreadPoolExecutorService">
        <property name="corePoolSize"  value="1" />
        <property name="maxPoolSize"   value="3" />
        <property name="keepAliveTime" value="10" />
        <property name="queueCapacity" value="3" />
        <property name="testRejectedExecutionHandler" ref="TestRejectedExecutionHandler" />
    </bean>
    <bean id="Starter" class="com.otv.start.Starter">
        <property name="threadPoolMonitorService" ref="ThreadPoolMonitorService" />
        <property name="testThreadPoolExecutorService" ref="TestThreadPoolExecutorService" />
    </bean>
</beans>

ШАГ 12: АЛЬТЕРНАТИВНЫЙ МЕТОД СОЗДАНИЯ РЕЗЬБОВОГО БАССЕЙНА

Класс ThreadPoolTaskExecutor, предоставляемый Spring, также можно использовать для создания пула потоков.

1
2
3
4
5
6
7
8
9
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize"  value="1" />
  <property name="maxPoolSize"   value="3" />
  <property name="queueCapacity" value="3" />
</bean>
  
<bean id="testTaskExecutor" class="TestTaskExecutor">
  <constructor-arg ref="threadPoolTaskExecutor" />
</bean>

ШАГ 13: СТРОИМ ПРОЕКТ

После сборки проекта OTV_Spring_ThreadPool будет создан файл OTV_Spring_ThreadPool-0.0.1-SNAPSHOT.jar .

ШАГ 14: ЗАПУСК ПРОЕКТА

После запуска созданного файла OTV_Spring_ThreadPool-0.0.1-SNAPSHOT.jar будут показаны следующие выходные журналы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
18.10.2011 20:08:48 DEBUG (TestRejectedExecutionHandler.java:19) - Task7 : has been rejected
18.10.2011 20:08:48 DEBUG (TestRejectedExecutionHandler.java:19) - Task8 : has been rejected
18.10.2011 20:08:48 DEBUG (TestRejectedExecutionHandler.java:19) - Task9 : has been rejected
18.10.2011 20:08:48 DEBUG (TestTask.java:25) - Task1 : is started.
18.10.2011 20:08:48 DEBUG (TestTask.java:25) - Task6 : is started.
18.10.2011 20:08:48 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 2 - CompletedTaskCount : 0 - TotalTaskCount : 5 - isTerminated : false
18.10.2011 20:08:48 DEBUG (TestTask.java:25) - Task5 : is started.
18.10.2011 20:08:53 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 0 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:08:58 DEBUG (TestTask.java:27) - Task6 : is completed.
18.10.2011 20:08:58 DEBUG (TestTask.java:27) - Task1 : is completed.
18.10.2011 20:08:58 DEBUG (TestTask.java:25) - Task3 : is started.
18.10.2011 20:08:58 DEBUG (TestTask.java:25) - Task2 : is started.
18.10.2011 20:08:58 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 2 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:08:58 DEBUG (TestTask.java:27) - Task5 : is completed.
18.10.2011 20:08:58 DEBUG (TestTask.java:25) - Task4 : is started.
18.10.2011 20:09:03 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 3 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:09:08 DEBUG (TestTask.java:27) - Task2 : is completed.
18.10.2011 20:09:08 DEBUG (TestTask.java:27) - Task3 : is completed.
18.10.2011 20:09:08 DEBUG (TestTask.java:27) - Task4 : is completed.
18.10.2011 20:09:08 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 6 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:09:13 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 6 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:09:18 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 0 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 6 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:09:23 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 0 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 6 - TotalTaskCount : 6 - isTerminated : false
18.10.2011 20:09:28 DEBUG (TestTask.java:25) - Task10 : is started.
18.10.2011 20:09:28 DEBUG (TestRejectedExecutionHandler.java:19) - Task16 : has been rejected
18.10.2011 20:09:28 DEBUG (TestRejectedExecutionHandler.java:19) - Task17 : has been rejected
18.10.2011 20:09:28 DEBUG (TestRejectedExecutionHandler.java:19) - Task18 : has been rejected
18.10.2011 20:09:28 DEBUG (TestTask.java:25) - Task14 : is started.
18.10.2011 20:09:28 DEBUG (TestTask.java:25) - Task15 : is started.
18.10.2011 20:09:28 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 6 - TotalTaskCount : 12 - isTerminated : false
18.10.2011 20:09:33 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 6 - TotalTaskCount : 12 - isTerminated : false
18.10.2011 20:09:38 DEBUG (TestTask.java:27) - Task10 : is completed.
18.10.2011 20:09:38 DEBUG (TestTask.java:25) - Task11 : is started.
18.10.2011 20:09:38 DEBUG (TestTask.java:27) - Task14 : is completed.
18.10.2011 20:09:38 DEBUG (TestTask.java:27) - Task15 : is completed.
18.10.2011 20:09:38 DEBUG (TestTask.java:25) - Task12 : is started.
18.10.2011 20:09:38 DEBUG (TestTask.java:25) - Task13 : is started.
18.10.2011 20:09:38 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 9 - TotalTaskCount : 12 - isTerminated : false
18.10.2011 20:09:43 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 3 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 3 - CompletedTaskCount : 9 - TotalTaskCount : 12 - isTerminated : false
18.10.2011 20:09:48 DEBUG (TestTask.java:27) - Task11 : is completed.
18.10.2011 20:09:48 DEBUG (TestTask.java:27) - Task13 : is completed.
18.10.2011 20:09:48 DEBUG (TestTask.java:27) - Task12 : is completed.
18.10.2011 20:09:48 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 0 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 12 - TotalTaskCount : 12 - isTerminated : true
18.10.2011 20:09:53 DEBUG (ThreadPoolMonitorService.java:39) - CurrentPoolSize : 0 - CorePoolSize : 1 - MaximumPoolSize : 3 - ActiveTaskCount : 0 - CompletedTaskCount : 12 - TotalTaskCount : 12 - isTerminated : true

ШАГ 15: СКАЧАТЬ

OTV_Spring_ThreadPool

Справка: Как разрабатывать и отслеживать службы пула потоков с помощью Spring от нашего партнера JCG