Skip to content

Commit eebaf80

Browse files
committed
+ coco4j
+ doc
1 parent 843041c commit eebaf80

File tree

12 files changed

+255
-280
lines changed

12 files changed

+255
-280
lines changed

README.md

Lines changed: 76 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ execution order at the same time.
2121

2222
## Prerequisite
2323

24-
Java 8 or better
24+
- Java 8 or better for versions before 20230922.20230925.0 (exclusive)
25+
- Java 21 or better for versions after 20230922.20230925.0 (inclusive)
2526

2627
## Get it...
2728

@@ -31,21 +32,21 @@ Install as a compile-scope dependency in Maven or other build tools alike.
3132

3233
## Use it...
3334

34-
### General notes
35+
Some general notes:
3536

36-
#### Sequence keys
37+
- Sequence keys
3738

3839
A sequence key cannot be `null`. Any two keys, `sequenceKey1` and `sequenceKey2`, are considered "the same sequence key"
3940
if and only if `Objects.equals(sequenceKey1, sequenceKey2)` returns `true`.
4041

41-
#### Thread safety
42+
- Thread safety
4243

4344
A conseq4j instance is thread-safe in and of itself. The usual thread-safety rules and concerns, however, still apply
4445
when programming the executable tasks. Moreover, in the context of concurrency and sequencing, the thread-safety concern
4546
goes beyond concurrent modification of individual-task data, into that of meaningful execution order among multiple
4647
related tasks.
4748

48-
#### Concurrency and sequencing
49+
- Concurrency and sequencing
4950

5051
First of all, by definition, there is no such thing as order or sequence among tasks submitted concurrently by different
5152
threads. No particular execution order is guaranteed on those concurrent tasks, regardless of their sequence keys. The
@@ -63,10 +64,10 @@ etc...
6364

6465
### Style 1: summon a sequential executor by its sequence key, then use the executor as with a JDK `ExecutorService`
6566

66-
#### API
67+
- API
6768

6869
```java
69-
public interface SequentialExecutorServiceFactory extends Terminable {
70+
public interface SequentialExecutorServiceFactory {
7071
/**
7172
* @param sequenceKey
7273
* an {@link Object} instance whose hash code is used to summon the corresponding executor.
@@ -77,40 +78,6 @@ public interface SequentialExecutorServiceFactory extends Terminable {
7778
}
7879
```
7980

80-
where ```Terminable``` is defined as
81-
82-
```java
83-
public interface Terminable {
84-
/**
85-
* Initiates an orderly shutdown of all managed thread resources. Previously submitted tasks are executed, but no
86-
* new tasks will be accepted. Invocation has no additional effect if already shut down.
87-
* <p>
88-
* This method does not wait for the previously submitted tasks to complete execution. Use an external awaiting
89-
* mechanism to do that, with the help of {@link #isTerminated()}.
90-
*/
91-
void shutdown();
92-
93-
/**
94-
* Non-blocking
95-
*
96-
* @return true if all tasks of all managed executors have completed following shut down. Note that isTerminated is
97-
* never true unless shutdown was called first.
98-
*/
99-
boolean isTerminated();
100-
101-
/**
102-
* Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the
103-
* tasks that were awaiting execution.
104-
* <p>
105-
* This method does not wait for the previously submitted tasks to complete execution. Use an external awaiting
106-
* mechanism to do that, with the help of {@link #isTerminated()}.
107-
*
108-
* @return Tasks submitted but never started executing
109-
*/
110-
List<Runnable> shutdownNow();
111-
}
112-
```
113-
11481
This API style loosely takes the form of "thread affinity". Sequence keys are used to summon executors of JDK
11582
type [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html). The same
11683
sequence key always gets back the same sequential executor. All tasks of that sequence key can then be "affined" to and
@@ -123,7 +90,7 @@ Consider using this style when the summoned executor needs to provide
12390
the [syntax and semantic richness](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#method.summary)
12491
of the JDK `ExecutorService` API.
12592

126-
#### Sample usage
93+
- Sample usage
12794

12895
```java
12996
public class MessageConsumer {
@@ -137,7 +104,8 @@ public class MessageConsumer {
137104
*/
138105
private final SequentialExecutorServiceFactory conseqServiceFactory = ConseqServiceFactory.instance();
139106

140-
@Autowired private ShoppingEventProcessor shoppingEventProcessor;
107+
@Autowired
108+
private ShoppingEventProcessor shoppingEventProcessor;
141109

142110
/**
143111
* Suppose run-time invocation of this method is managed by the messaging provider. This is usually via a single
@@ -154,42 +122,46 @@ public class MessageConsumer {
154122
}
155123
```
156124

157-
- The implementation of this thread-affinity style relies on hashing of the sequence keys into a fixed number of
158-
"buckets". These buckets are each associated with a sequential executor. The same sequence key is always hashed to and
159-
summons back the same executor. Single-threaded, each executor ensures the execution order of all its tasks is the
160-
same as they are submitted; excessive tasks pending execution are buffered in a FIFO task queue. Thus, the total
161-
number of buckets (i.e. the max number of available executors and the general concurrency) is the maximum number of
162-
tasks that can be executed in parallel at any given time.
163-
- As with hashing, collision may occur among different sequence keys. When hash collision happens, tasks of different
164-
sequence keys are assigned to the same executor. Due to the single-thread setup, the executor still ensures the local
165-
sequential execution order for each individual sequence key's tasks. However, unrelated tasks of different sequence
166-
keys now assigned to the same bucket/executor may delay each other's execution inadvertently while waiting in the
167-
executor's task queue. Consider this a trade-off of the executor's having the same syntax and semantic richness as a
168-
JDK [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html).
169-
- To account for hash collision, conseq4j does not support any shutdown action on the API-provided
170-
executor ([ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html))
171-
instance. That is to prevent unintended task cancellation across different sequence keys.
172-
The [Future](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html) instance(s) subsequently
173-
returned by the executor, though, is still cancellable. The hash collision may not be an issue for workloads that are
174-
asynchronous and focused on overall through-put, but is something to be aware of.
175-
- The default general concurrency is either 16 or the JVM
176-
run-time's [availableProcessors](https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors--),
177-
which ever is larger:
125+
The implementation of this thread-affinity style relies on hashing of the sequence keys into a fixed number of
126+
"buckets". These buckets are each associated with a sequential executor. The same sequence key is always hashed to and
127+
summons back the same executor. Single-threaded, each executor ensures the execution order of all its tasks is the
128+
same as they are submitted; excessive tasks pending execution are buffered in a FIFO task queue. Thus, the total
129+
number of buckets (i.e. the max number of available executors and the general concurrency) is the maximum number of
130+
tasks that can be executed in parallel at any given time.
131+
132+
As with hashing, collision may occur among different sequence keys. When hash collision happens, tasks of different
133+
sequence keys are assigned to the same executor. Due to the single-thread setup, the executor still ensures the local
134+
sequential execution order for each individual sequence key's tasks. However, unrelated tasks of different sequence
135+
keys now assigned to the same bucket/executor may delay each other's execution inadvertently while waiting in the
136+
executor's task queue. Consider this a trade-off of the executor's having the same syntax and semantic richness as a
137+
JDK [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html).
138+
139+
To account for hash collision, conseq4j Style 1 does not support any shutdown action on the API-provided
140+
executor ([ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html))
141+
instance. That is to prevent unintended task cancellation across different sequence keys.
142+
The [Future](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html) instance(s) subsequently
143+
returned by the executor, though, is still cancellable. The hash collision may not be an issue for workloads that are
144+
asynchronous and focused on overall through-put, but is something to be aware of.
145+
146+
The default general concurrency is the JVM
147+
run-time's [availableProcessors](https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors--):
148+
178149
```jshelllanguage
179150
ConseqServiceFactory.instance();
180151
```
181152

182-
The concurrency can be customized:
153+
The concurrency can be customized:
154+
183155
```jshelllanguage
184156
ConseqServiceFactory.instance(10)
185157
```
186158

187159
### Style 2: submit each task directly for execution, together with its sequence key
188160

189-
#### API
161+
- API
190162

191163
```java
192-
public interface SequentialExecutor extends Terminable {
164+
public interface SequentialExecutor {
193165
/**
194166
* @param command
195167
* the Runnable task to run sequentially with others under the same sequence key
@@ -216,33 +188,32 @@ This API style is more concise. Bypassing the JDK ExecutorService API, it servic
216188
execution semantics holds: Tasks of the same sequence key are executed in the same submission order; tasks of different
217189
sequence keys are managed to execute in parallel.
218190

219-
Prefer this style when the full-blown syntax and semantic support of
191+
For versions requiring Java 21+, conseq4j Style 2 defaults to have no preset limit on the overall concurrency when
192+
executing tasks; for other versions, this style's default concurrency is the number of JVM run-time'
193+
s [availableProcessors](https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors--). Prefer
194+
this style when the full-blown syntax and semantic support of
220195
JDK [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) is not
221196
required.
222197

223-
#### Sample usage
198+
- Sample usage
224199

225200
```java
226201
public class MessageConsumer {
227202

228203
/**
229-
* Default executor concurrency is java.lang.Runtime.availableProcessors.
230-
* <p>
231-
* Or to provide a custom concurrency of 10, for example:
232-
* <code>
233-
* private SequentialExecutor conseqExecutor = ConseqExecutor.instance(10));
234-
* </code>
204+
* Default executor has no preset limit on overall concurrency, running on virtual thread per task.
235205
*/
236206
private final SequentialExecutor conseqExecutor = ConseqExectuor.instance();
237207

238-
@Autowired private ShoppingEventProcessor shoppingEventProcessor;
208+
@Autowired
209+
private ShoppingEventProcessor shoppingEventProcessor;
239210

240211
/**
241-
* Suppose run-time invocation of this method is managed by the messaging provider. This is usually via a single
212+
* Suppose run-time invocation of this method is managed by the messaging provider. This is usually via a single
242213
* caller thread.
243214
* <p>
244-
* Concurrency is achieved when shopping events of different shopping cart IDs are processed in parallel by
245-
* different backing threads. Sequence is maintained for all shopping events of the same shopping cart ID, via
215+
* Concurrency is achieved when shopping events of different shopping cart IDs are processed in parallel by
216+
* different backing threads. Sequence is maintained for all shopping events of the same shopping cart ID, via
246217
* linear progression of execution stages with {@link java.util.concurrent.CompletableFuture}.
247218
*/
248219
public void onMessage(Message shoppingEvent) {
@@ -251,32 +222,39 @@ public class MessageConsumer {
251222
}
252223
```
253224

254-
- The interface of this direct-execute style uses `Future` as the return type, mainly to reduce conceptual weight of the
255-
API. The implementation actually returns `CompletableFuture`, and can be used/cast as such if need be.
256-
- The implementation relies on
257-
JDK's [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) to
258-
achieve sequential execution of related tasks. One single pool of threads is used to facilitate the overall
259-
asynchronous execution. The concurrency to execute unrelated tasks is generally limited only by the backing work
260-
thread pool's capacity.
261-
- Instead of thread-affinity or bucket hashing, tasks are decoupled from their execution threads. All pooled threads are
262-
anonymous and interchangeable to execute any tasks. Even sequential tasks of the same sequence key may be executed by
263-
different threads, albeit in sequential order. When the work thread pool has idle threads available, a task awaiting
264-
execution must have been blocked only by its own related task(s) of the same sequence key - as is necessary, and not
265-
by unrelated tasks of different sequence keys in the same "bucket" - as is unnecessary. This can be a desired
266-
advantage over the thread-affinity API style, at the trade-off of lesser syntax and semantic richness than the
267-
JDK [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html).
268-
- The default general concurrency (i.e. the execution work thread pool capacity is the JVM
269-
run-time's [availableProcessors](https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors--)
225+
The implementation relies on
226+
JDK's [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) to
227+
achieve sequential execution of related tasks. One single pool of threads is used to facilitate the overall
228+
asynchronous execution. The concurrency to execute unrelated tasks is generally limited only by the backing work
229+
thread pool's capacity, or unlimited in the case of default/virtual thread mode.
230+
231+
Instead of thread-affinity or bucket hashing, tasks are decoupled from their execution threads. All pooled threads are
232+
anonymous and interchangeable to execute any tasks. Even sequential tasks of the same sequence key may be executed by
233+
different threads, albeit in sequential order. When the work thread pool has idle threads available, a task awaiting
234+
execution must have been blocked only by its own related task(s) of the same sequence key - as is necessary, and not
235+
by unrelated tasks of different sequence keys in the same "bucket" - as is unnecessary. This can be a desired
236+
advantage over the thread-affinity API style, at the trade-off of lesser syntax and semantic richness than the
237+
JDK [ExecutorService](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html).
238+
239+
For versions requiring Java 21+, the default ConseqExecutor instance uses Virtual thread per task, and has no preset
240+
limit on overall concurrency. For other versions, the default concurrency is the number of JVM's available processors.
241+
270242
```jshelllanguage
271243
ConseqExecutor.instance()
272244
```
273-
The concurrency can be customized:
245+
246+
The concurrency/parallelism can be customized to use a Platform thread `ForkJoinPool` of the specified capacity:
247+
274248
```jshelllanguage
275249
ConseqExecutor.instance(10)
276250
```
277-
- The `ConseqExecutor` instance can also be powered by a fully-customized `ExecutorService`:
278251

279-
`ConseqExecutor.instance(ExecutorService workerExecutorService)`
252+
The `ConseqExecutor` instance can also use a fully-customized (Virtual or Platform thread-based) `ExecutorService` to
253+
power its async operations, e.g. with a fixed sized platform-thread pool:
254+
255+
```jshelllanguage
256+
ConseqExecutor.instance(Executors.newFixedThreadPool(10))
257+
```
280258

281259
## Full disclosure - Asynchronous Conundrum
282260

pom.xml

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<modelVersion>4.0.0</modelVersion>
2828
<groupId>io.github.q3769</groupId>
2929
<artifactId>conseq4j</artifactId>
30-
<version>20230922.20230924.20230924</version>
30+
<version>20230922.20230924.20240512</version>
3131
<packaging>jar</packaging>
3232
<name>conseq4j</name>
3333
<description>A Java concurrent API to sequence related tasks while concurring unrelated ones</description>
@@ -129,7 +129,7 @@
129129
<plugin>
130130
<groupId>io.github.q3769</groupId>
131131
<artifactId>semver-maven-plugin</artifactId>
132-
<version>20221011.20230506.20230611</version>
132+
<version>20240116.0.202402</version>
133133
</plugin>
134134
<plugin>
135135
<groupId>org.apache.maven.plugins</groupId>
@@ -149,25 +149,44 @@
149149
</execution>
150150
</executions>
151151
</plugin>
152+
<plugin>
153+
<groupId>com.diffplug.spotless</groupId>
154+
<artifactId>spotless-maven-plugin</artifactId>
155+
<version>2.43.0</version>
156+
<executions>
157+
<execution>
158+
<id>spotless-apply-id</id>
159+
<phase>process-sources</phase>
160+
<goals>
161+
<goal>apply</goal>
162+
</goals>
163+
</execution>
164+
</executions>
165+
<configuration>
166+
<java>
167+
<palantirJavaFormat>
168+
<version>2.46.0</version>
169+
<style>PALANTIR</style>
170+
<formatJavadoc>true</formatJavadoc>
171+
</palantirJavaFormat>
172+
<removeUnusedImports/>
173+
<formatAnnotations/>
174+
</java>
175+
</configuration>
176+
</plugin>
152177
</plugins>
153178
</build>
154179
<dependencies>
155-
<dependency>
156-
<groupId>com.google.code.findbugs</groupId>
157-
<artifactId>jsr305</artifactId>
158-
<version>3.0.2</version>
159-
<scope>provided</scope>
160-
</dependency>
161180
<dependency>
162181
<groupId>org.projectlombok</groupId>
163182
<artifactId>lombok</artifactId>
164-
<version>1.18.30</version>
183+
<version>1.18.32</version>
165184
<scope>provided</scope>
166185
</dependency>
167186
<dependency>
168187
<groupId>org.junit.jupiter</groupId>
169188
<artifactId>junit-jupiter-api</artifactId>
170-
<version>5.9.2</version>
189+
<version>5.9.3</version>
171190
<scope>test</scope>
172191
</dependency>
173192
<dependency>
@@ -199,6 +218,11 @@
199218
<version>32.0.1-jre</version>
200219
<scope>test</scope>
201220
</dependency>
221+
<dependency>
222+
<groupId>io.github.q3769</groupId>
223+
<artifactId>coco4j</artifactId>
224+
<version>10.0.0</version>
225+
</dependency>
202226
</dependencies>
203227
<properties>
204228
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

0 commit comments

Comments
 (0)