Introduction
Some services work asynchronously, hence they require thread pools to perform their tasks. All thread pooling
facilities are centralized behind the ExecutionService
interface.
Let’s start with a bit of theory
What ExecutionService
provides
ExecutionService
is an interface providing:
-
ScheduledExecutorService
to schedule tasks, i.e.: tasks that happen repeatedly after a configurable delay. -
Unordered
ExecutorService
to execute tasks as soon as a thread is available. -
Ordered
ExecutorService
to execute tasks as soon as a thread is available, with the guarantee that tasks are going to be executed in the order they were submitted.
Available ExecutionService
implementations
There currently are two bundled implementations:
-
OnDemandExecutionService
creates a new pool each time an executor service (scheduled or not) is requested. This implementation is the default one and requires no configuration at all. -
PooledExecutionService
keeps a configurable set of thread pools and divides them to handle all executor service requests. This implementation must be configured with aPooledExecutionServiceConfiguration
when used.
Configuring PooledExecutionService
When you want total control of the threads used by a cache manager and its caches, you have to use a
PooledExecutionService
that itself must be configured as it does not have any defaults.
The PooledExecutionServiceConfigurationBuilder
can be used for this purpose, and the resulting configuration it builds
can simply be added to a CacheManagerBuilder
to switch the ExecutionService
implementation to a
PooledExecutionService
.
The builder has two interesting methods:
-
defaultPool
that is used to set the default pool. There can be only one default pool, its name does not matter, and if thread-using services do not specify a thread pool, this is the one that will be used. -
pool
that is used to add a thread pool. There can be as many pools as you wish but services must explicitly be configured to make use of them.
Using the configured thread pools
Following is the list of services making use of ExecutionService
:
-
Disk store: disk writes are performed asynchronously.
OffHeapDiskStoreConfiguration
is used to configure what thread pool to use at the cache level, whileOffHeapDiskStoreProviderConfiguration
is used to configure what thread pool to use at the cache manager level. -
Write Behind:
CacheLoaderWriter
write tasks happen asynchronously.DefaultWriteBehindConfiguration
is used to configure what thread pool to use at the cache level, whileWriteBehindProviderConfiguration
is used to configure what thread pool to use at the cache manager level. -
Eventing: produced events are queued and sent to the listeners by a thread pool.
DefaultCacheEventDispatcherConfiguration
is used to configure what thread pool to use at the cache level, whileCacheEventDispatcherFactoryConfiguration
is used to configure what thread pool to use at the cache manager level.
The different builders will make use of the right configuration class, you do not have to use those classes directly.
For instance, calling CacheManagerBuilder.withDefaultDiskStoreThreadPool(String threadPoolAlias)
is actually identical
to calling CacheManagerBuilder.using(new OffHeapDiskStoreProviderConfiguration(threadPoolAlias))
.
The thread pool can be assigned to a service with the builders by passing a
threadPoolAlias
parameter to the ad-hoc method. When a service isn’t told anything about what thread pool to use,
the default thread pool is used.
In practice
Following are examples of describing how to configure the thread pools the different services will use.
Configuring with code
Disk store
CacheManager cacheManager
= CacheManagerBuilder.newCacheManagerBuilder()
.using(PooledExecutionServiceConfigurationBuilder.newPooledExecutionServiceConfigurationBuilder() (1)
.defaultPool("dflt", 0, 10)
.pool("defaultDiskPool", 1, 3)
.pool("cache2Pool", 2, 2)
.build())
.with(new CacheManagerPersistenceConfiguration(new File(getStoragePath(), "myData")))
.withDefaultDiskStoreThreadPool("defaultDiskPool") (2)
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.disk(10L, MemoryUnit.MB)))
.withCache("cache2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.disk(10L, MemoryUnit.MB))
.withDiskStoreThreadPool("cache2Pool", 2)) (3)
.build(true);
Cache<Long, String> cache1 =
cacheManager.getCache("cache1", Long.class, String.class);
Cache<Long, String> cache2 =
cacheManager.getCache("cache2", Long.class, String.class);
cacheManager.close();
1 | Configure the thread pools. Note that the default one (dflt ) is required for the events even when no event
listener is configured. |
2 | Tell the CacheManagerBuilder to use a default thread pool for all disk stores that don’t explicitly specify one. |
3 | Tell the cache to use a specific thread pool for its disk store. |
Write Behind
CacheManager cacheManager
= CacheManagerBuilder.newCacheManagerBuilder()
.using(PooledExecutionServiceConfigurationBuilder.newPooledExecutionServiceConfigurationBuilder() (1)
.defaultPool("dflt", 0, 10)
.pool("defaultWriteBehindPool", 1, 3)
.pool("cache2Pool", 2, 2)
.build())
.withDefaultWriteBehindThreadPool("defaultWriteBehindPool") (2)
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES))
.withLoaderWriter(new SampleLoaderWriter<Long, String>(singletonMap(41L, "zero")))
.add(WriteBehindConfigurationBuilder
.newBatchedWriteBehindConfiguration(1, TimeUnit.SECONDS, 3)
.queueSize(3)
.concurrencyLevel(1)))
.withCache("cache2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES))
.withLoaderWriter(new SampleLoaderWriter<Long, String>(singletonMap(41L, "zero")))
.add(WriteBehindConfigurationBuilder
.newBatchedWriteBehindConfiguration(1, TimeUnit.SECONDS, 3)
.useThreadPool("cache2Pool") (3)
.queueSize(3)
.concurrencyLevel(2)))
.build(true);
Cache<Long, String> cache1 =
cacheManager.getCache("cache1", Long.class, String.class);
Cache<Long, String> cache2 =
cacheManager.getCache("cache2", Long.class, String.class);
cacheManager.close();
1 | Configure the thread pools. Note that the default one (dflt ) is required for the events even when no event
listener is configured. |
2 | Tell the CacheManagerBuilder to use a default thread pool for all write-behind caches that don’t explicitly
specify one. |
3 | Tell the WriteBehindConfigurationBuilder to use a specific thread pool for its write-behind work. |
Events
CacheManager cacheManager
= CacheManagerBuilder.newCacheManagerBuilder()
.using(PooledExecutionServiceConfigurationBuilder.newPooledExecutionServiceConfigurationBuilder() (1)
.pool("defaultEventPool", 1, 3)
.pool("cache2Pool", 2, 2)
.build())
.withDefaultEventListenersThreadPool("defaultEventPool") (2)
.withCache("cache1",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES))
.add(CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED)))
.withCache("cache2",
CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES))
.add(CacheEventListenerConfigurationBuilder
.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED))
.withEventListenersThreadPool("cache2Pool")) (3)
.build(true);
Cache<Long, String> cache1 =
cacheManager.getCache("cache1", Long.class, String.class);
Cache<Long, String> cache2 =
cacheManager.getCache("cache2", Long.class, String.class);
cacheManager.close();
1 | Configure the thread pools. Note that there is no default one so all thread-using services must be configured with explicit defaults. |
2 | Tell the CacheManagerBuilder to use a default thread pool to manage events of all caches that don’t explicitly
specify one. |
3 | Tell the CacheEventListenerConfigurationBuilder to use a specific thread pool for sending its events. |
Configuring with XML
<thread-pools> (1)
<thread-pool alias="defaultDiskPool" min-size="1" max-size="3"/>
<thread-pool alias="defaultWriteBehindPool" min-size="1" max-size="3"/>
<thread-pool alias="cache2Pool" min-size="2" max-size="2"/>
</thread-pools>
<event-dispatch thread-pool="defaultEventPool"/> (2)
<write-behind thread-pool="defaultWriteBehindPool"/> (3)
<disk-store thread-pool="defaultDiskPool"/> (4)
<cache alias="cache1">
<key-type>java.lang.Long</key-type>
<value-type>java.lang.String</value-type>
<resources>
<heap unit="entries">10</heap>
<disk unit="MB">10</disk>
</resources>
</cache>
<cache alias="cache2">
<key-type>java.lang.Long</key-type>
<value-type>java.lang.String</value-type>
<loader-writer>
<class>org.ehcache.docs.plugs.ListenerObject</class>
<write-behind thread-pool="cache2Pool"> (5)
<batching batch-size="5">
<max-write-delay unit="seconds">10</max-write-delay>
</batching>
</write-behind>
</loader-writer>
<listeners dispatcher-thread-pool="cache2Pool"/> (6)
<resources>
<heap unit="entries">10</heap>
<disk unit="MB">10</disk>
</resources>
<disk-store-settings thread-pool="cache2Pool" writer-concurrency="2"/> (7)
</cache>
1 | Configure the thread pools. Note that there is no default one. |
2 | Configure the default thread pool this cache manager will use to send events. |
3 | Configure the default thread pool this cache manager will use for write-behind work. |
4 | Configure the default thread pool this cache manager will use for disk stores. |
5 | Configure a specific write-behind thread pool for this cache. |
6 | Configure a specific thread pool for this cache to send its events. |
7 | Configure a specific thread pool for this cache’s disk store. |