WorkerPoolModule in particular) is a handy way of
injecting multiple dependencies of the same type. It is useful when you need, for example, same set of dependencies for each thread. ActiveJ heavily utilizes workers
to implement multithreading. Workers are the base of ActiveJ threading model.
The primary mission of ActiveJ is to create ultimately fast, scalable, simple to use, and high-abstraction level I/O async
To achieve this, ActiveJ design principles overcome all the performance overhead and complexities of the traditional
multithreaded programming model, yet fully utilize Java multithreading capabilities.
ActiveJ offers means of splitting the application into a
Primary Eventloop thread and
Eventloop threads. These threads communicate with each other via message passing and thread-safe application-specific
Eventloop thread is essentially a single-threaded mini-application
(similar to Node.js), which handles its part of I/O tasks and executes Runnables submitted from other threads.
Primary Eventloop threads distribute and balance I/O tasks between Worker threads.
The benefits of ActiveJ threading model:
- Each primary/worker Eventloop thread works as a single-threaded application, which is simple to program and to reason about
- There is no multithreaded overhead, races, and thread synchronization overhead
- Traditional strength of Java in multithreaded programming is fully preserved:
- typical I/O load can be easily split between worker threads
- the application can have thread-safe singleton services, which are used by Eventloop threads, and a huge singleton data state, shared among all worker threads
- you can still use some thread synchronization / lock-free algorithms, just try to avoid excessive blocking of concurrent threads
- full interoperability between Java Threads, Thread Pools, Java Futures, and even blocking I/O operations
This design raises some implementation questions. For example, if we want to implement multithreaded HTTP web application with worker eventloops:
- according to these design principles, we need to create separate instances of a working eventloop, a single-threaded HTTP server, and its servlets for each working thread
- but what if our application has 8 eventloop threads with 10 worker-thread components inside, do we have to create 80 of components in total and assign them to each worker thread?
- how is it even possible to manually instantiate, wire, initialize, and start/stop all those components in a correct order and also gracefully shutdown application on start/stop errors?
Luckily, due to ActiveJ Inject, we have a solution -
@Worker scope. If you need to implement several worker threads:
WorkerPoolModulemodule and create a
- annotate the components you wish to put into each worker thread with
WorkerPool will automatically instantiate identical dependency graphs for each of those worker threads You are by no means limited to the aforementioned scheme with one primary Eventloop and N worker eventloops:
- you can still have completely unrelated / standalone eventloops (nor primary, neither worker)
- several primary eventloops sharing the same pool of worker eventloops or several sets of worker pools with different number or threads
- you can even define your own
@Workerannotations, and create multiple worker pools with completely unrelated and different dependency graphs All this is in fully transparent and easy-to-understand modules - just mark different components with appropriate worker annotations and let
WorkerPoolcreate all the instances
To automatically start/stop application components in correct order, simply include ServiceGraph module into your Launcher - it is aware of worker pools and will treat vectors of worker instances as special compound singleton-like instances.
For example, here is an example of utilizing
MultithreadedHttpServerLauncher which features
And its dependency graph looks as follows:
To help you understand how worker pools work, here is a simplified WorkerPool implementation in a nutshell (the actual implementation differs, but not much):
As you can see, the root
simply ‘enters’ the worker scope
N times, so we have
Injectors with identical bindings/dependency graphs, but
different containers of instances. Each time we need to create some worker instances, they are created
N times by
each injector and returned as a vector of
To run the examples, you need to clone ActiveJ from GitHub
And import it as a Maven project. Check out tag . Before running the examples, build the project. These examples are located at activej -> examples -> core -> boot
An example of creating a worker pool with 4 workers:
The dependency graph of the example includes the created worker pool and looks as follows:
Several Worker Pools can co-work to calculate a single task. In this example we have 25 Workers and each of them has its own Eventloop. These Eventloops are wrapped in Threads and then added to the list of threads. After that the list is permuted and the threads with Eventloop tasks start. The task is to put Eventloop id in the ConcurrentLinkedQueue in accordance to the delay (the id multiplied by 100). In this way we receive an ordered queue of Eventloop ids, after that the Threads park and the queue is emptied.