18
18
import java .util .IdentityHashMap ;
19
19
import java .util .List ;
20
20
import java .util .Set ;
21
+ import java .util .WeakHashMap ;
21
22
import java .util .concurrent .Callable ;
22
23
import java .util .concurrent .CompletableFuture ;
23
24
import java .util .concurrent .ExecutionException ;
31
32
import java .util .concurrent .ScheduledThreadPoolExecutor ;
32
33
import java .util .concurrent .TimeUnit ;
33
34
import java .util .concurrent .TimeoutException ;
35
+ import java .util .concurrent .atomic .AtomicInteger ;
34
36
import java .util .function .BiFunction ;
35
37
import java .util .function .Function ;
36
38
39
+ import org .eclipse .jdt .annotation .NonNull ;
37
40
import org .eclipse .jdt .annotation .NonNullByDefault ;
38
41
import org .eclipse .jdt .annotation .Nullable ;
39
42
50
53
* @author Jörg Sautter - Initial contribution
51
54
*/
52
55
@ NonNullByDefault
53
- class PoolBasedSequentialScheduledExecutorService implements ScheduledExecutorService {
56
+ final class PoolBasedSequentialScheduledExecutorService implements ScheduledExecutorService {
57
+
58
+ private static final WeakHashMap <ScheduledThreadPoolExecutor , @ NonNull AtomicInteger > PENDING_BY_POOL = new WeakHashMap <>();
54
59
55
60
private final WorkQueueEntry empty ;
56
61
private final ScheduledThreadPoolExecutor pool ;
62
+ private final AtomicInteger pending ;
57
63
private final List <RunnableFuture <?>> scheduled ;
58
64
private final ScheduledFuture <?> cleaner ;
59
65
private @ Nullable WorkQueueEntry tail ;
@@ -75,6 +81,20 @@ public PoolBasedSequentialScheduledExecutorService(ScheduledThreadPoolExecutor p
75
81
76
82
tail = empty ;
77
83
84
+ // we need one pending counter per pool
85
+ synchronized (PENDING_BY_POOL ) {
86
+ AtomicInteger fromCache = PENDING_BY_POOL .get (pool );
87
+
88
+ if (fromCache == null ) {
89
+ // set to one does ensure at least one thread more than tasks running
90
+ fromCache = new AtomicInteger (1 );
91
+
92
+ PENDING_BY_POOL .put (pool , fromCache );
93
+ }
94
+
95
+ pending = fromCache ;
96
+ }
97
+
78
98
// clean up to ensure we do not keep references to old tasks
79
99
cleaner = this .scheduleWithFixedDelay (() -> {
80
100
synchronized (this ) {
@@ -100,22 +120,24 @@ public PoolBasedSequentialScheduledExecutorService(ScheduledThreadPoolExecutor p
100
120
tail = empty ;
101
121
}
102
122
}
103
- }, 2 , 4 , TimeUnit .SECONDS );
123
+ },
124
+ // avoid cleaners of promptly created instances to run at the same time
125
+ (System .nanoTime () % 13 ), 8 , TimeUnit .SECONDS );
104
126
}
105
127
106
128
@ Override
107
129
public ScheduledFuture <?> schedule (@ Nullable Runnable command , long delay , @ Nullable TimeUnit unit ) {
108
130
return schedule ((origin ) -> pool .schedule (() -> {
109
- // we block the thread here, in worst case new threads are spawned
110
- submitToWorkQueue (origin .join (), command ).join ();
131
+ // we might block the thread here, in worst case new threads are spawned
132
+ submitToWorkQueue (origin .join (), command , true ).join ();
111
133
}, delay , unit ));
112
134
}
113
135
114
136
@ Override
115
137
public <V > ScheduledFuture <V > schedule (@ Nullable Callable <V > callable , long delay , @ Nullable TimeUnit unit ) {
116
138
return schedule ((origin ) -> pool .schedule (() -> {
117
- // we block the thread here, in worst case new threads are spawned
118
- return submitToWorkQueue (origin .join (), callable ).join ();
139
+ // we might block the thread here, in worst case new threads are spawned
140
+ return submitToWorkQueue (origin .join (), callable , true ).join ();
119
141
}, delay , unit ));
120
142
}
121
143
@@ -126,13 +148,13 @@ public ScheduledFuture<?> scheduleAtFixedRate(@Nullable Runnable command, long i
126
148
CompletableFuture <?> submitted ;
127
149
128
150
try {
129
- // we block the thread here, in worst case new threads are spawned
130
- submitted = submitToWorkQueue (origin .join (), command );
151
+ submitted = submitToWorkQueue (origin .join (), command , true );
131
152
} catch (RejectedExecutionException ex ) {
132
153
// the pool has been shutdown, scheduled tasks should cancel
133
154
return ;
134
155
}
135
156
157
+ // we might block the thread here, in worst case new threads are spawned
136
158
submitted .join ();
137
159
}, initialDelay , period , unit ));
138
160
}
@@ -144,13 +166,13 @@ public ScheduledFuture<?> scheduleWithFixedDelay(@Nullable Runnable command, lon
144
166
CompletableFuture <?> submitted ;
145
167
146
168
try {
147
- // we block the thread here, in worst case new threads are spawned
148
- submitted = submitToWorkQueue (origin .join (), command );
169
+ submitted = submitToWorkQueue (origin .join (), command , true );
149
170
} catch (RejectedExecutionException ex ) {
150
171
// the pool has been shutdown, scheduled tasks should cancel
151
172
return ;
152
173
}
153
174
175
+ // we might block the thread here, in worst case new threads are spawned
154
176
submitted .join ();
155
177
}, initialDelay , delay , unit ));
156
178
}
@@ -255,20 +277,21 @@ public boolean awaitTermination(long timeout, @Nullable TimeUnit unit) throws In
255
277
256
278
@ Override
257
279
public <T > Future <T > submit (@ Nullable Callable <T > task ) {
258
- return submitToWorkQueue (null , task );
280
+ return submitToWorkQueue (null , task , false );
259
281
}
260
282
261
- private CompletableFuture <?> submitToWorkQueue (RunnableFuture <?> origin , @ Nullable Runnable task ) {
283
+ private CompletableFuture <?> submitToWorkQueue (RunnableFuture <?> origin , @ Nullable Runnable task , boolean inPool ) {
262
284
Callable <?> callable = () -> {
263
285
task .run ();
264
286
265
287
return null ;
266
288
};
267
289
268
- return submitToWorkQueue (origin , callable );
290
+ return submitToWorkQueue (origin , callable , inPool );
269
291
}
270
292
271
- private <T > CompletableFuture <T > submitToWorkQueue (@ Nullable RunnableFuture <?> origin , @ Nullable Callable <T > task ) {
293
+ private <T > CompletableFuture <T > submitToWorkQueue (@ Nullable RunnableFuture <?> origin , @ Nullable Callable <T > task ,
294
+ boolean inPool ) {
272
295
BiFunction <? super Object , Throwable , T > action = (result , error ) -> {
273
296
// ignore result & error, they are from the previous task
274
297
try {
@@ -278,22 +301,45 @@ private <T> CompletableFuture<T> submitToWorkQueue(@Nullable RunnableFuture<?> o
278
301
} catch (Exception ex ) {
279
302
// a small hack to throw the Exception unchecked
280
303
throw PoolBasedSequentialScheduledExecutorService .unchecked (ex );
304
+ } finally {
305
+ pending .decrementAndGet ();
281
306
}
282
307
};
283
308
309
+ RunnableCompletableFuture <T > cf ;
310
+ boolean runNow ;
311
+
284
312
synchronized (this ) {
285
313
if (tail == null ) {
286
314
throw new RejectedExecutionException ("this scheduled executor has been shutdown before" );
287
315
}
288
316
289
- RunnableCompletableFuture <T > cf = tail .future .handleAsync (action , pool );
317
+ // set the core pool size even if it does not change, this triggers idle threads to stop
318
+ pool .setCorePoolSize (pending .incrementAndGet ());
290
319
291
- cf .setCallable (task );
320
+ // avoid waiting for one pool thread to finish inside a pool thread
321
+ runNow = inPool && tail .future .isDone ();
292
322
293
- tail = new WorkQueueEntry (tail , origin , cf );
323
+ if (runNow ) {
324
+ cf = new RunnableCompletableFuture <>(task );
325
+ tail = new WorkQueueEntry (null , origin , cf );
326
+ } else {
327
+ cf = tail .future .handleAsync (action , pool );
328
+ cf .setCallable (task );
329
+ tail = new WorkQueueEntry (tail , origin , cf );
330
+ }
331
+ }
294
332
295
- return cf ;
333
+ if (runNow ) {
334
+ // ensure we do not wait for one pool thread to finish inside another pool thread
335
+ try {
336
+ cf .run ();
337
+ } finally {
338
+ pending .decrementAndGet ();
339
+ }
296
340
}
341
+
342
+ return cf ;
297
343
}
298
344
299
345
private static <E extends RuntimeException > E unchecked (Exception ex ) throws E {
@@ -306,7 +352,7 @@ public <T> Future<T> submit(@Nullable Runnable task, T result) {
306
352
task .run ();
307
353
308
354
return result ;
309
- });
355
+ }, false );
310
356
}
311
357
312
358
@ Override
@@ -343,7 +389,7 @@ public <T> List<Future<T>> invokeAll(@Nullable Collection<? extends @Nullable Ca
343
389
List <Future <T >> futures = new ArrayList <>();
344
390
345
391
for (Callable <T > task : tasks ) {
346
- futures .add (submitToWorkQueue (null , task ).orTimeout (timeout , unit ));
392
+ futures .add (submitToWorkQueue (null , task , false ).orTimeout (timeout , unit ));
347
393
}
348
394
349
395
// wait for all futures to complete
@@ -381,7 +427,7 @@ private <T> T invokeAny(@Nullable Collection<? extends @Nullable Callable<T>> ta
381
427
List <CompletableFuture <T >> futures = new ArrayList <>();
382
428
383
429
for (Callable <T > task : tasks ) {
384
- futures .add (submitToWorkQueue (null , task ));
430
+ futures .add (submitToWorkQueue (null , task , false ));
385
431
}
386
432
387
433
// wait for any future to complete
@@ -452,7 +498,11 @@ static class RunnableCompletableFuture<V> extends CompletableFuture<V> implement
452
498
private @ Nullable Callable <V > callable ;
453
499
454
500
public RunnableCompletableFuture () {
455
- callable = null ;
501
+ this .callable = null ;
502
+ }
503
+
504
+ public RunnableCompletableFuture (@ Nullable Callable <V > callable ) {
505
+ this .callable = callable ;
456
506
}
457
507
458
508
public void setCallable (@ Nullable Callable <V > callable ) {
0 commit comments