Skip to content

Commit 6865c52

Browse files
committed
Use ForkJoinPool as executor service.
1 parent 7d5f6ca commit 6865c52

File tree

3 files changed

+71
-37
lines changed

3 files changed

+71
-37
lines changed

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.concurrent.Callable;
3737
import java.util.concurrent.ConcurrentHashMap;
3838
import java.util.concurrent.ExecutorService;
39+
import java.util.concurrent.Executors;
3940
import java.util.concurrent.Future;
4041
import java.util.concurrent.TimeUnit;
4142

@@ -227,7 +228,7 @@ public static boolean isSyncServerAvailable() {
227228
private final int[] allEntityTypeIds;
228229
private final Map<Class<?>, Box<?>> boxes = new ConcurrentHashMap<>();
229230
private final Set<Transaction> transactions = Collections.newSetFromMap(new WeakHashMap<>());
230-
private final ExecutorService threadPool = new ObjectBoxThreadPool(this);
231+
private final ExecutorService threadPool;
231232
private final ObjectClassPublisher objectClassPublisher;
232233
final boolean debugTxRead;
233234
final boolean debugTxWrite;
@@ -257,6 +258,8 @@ public static boolean isSyncServerAvailable() {
257258
private SyncClient syncClient;
258259

259260
BoxStore(BoxStoreBuilder builder) {
261+
threadPool = Executors.unconfigurableExecutorService(
262+
new ObjectBoxThreadPool(this, builder.executorServiceParallelism));
260263
context = builder.context;
261264
relinker = builder.relinker;
262265
NativeLibraryLoader.ensureLoaded();

objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.ArrayList;
3232
import java.util.Arrays;
3333
import java.util.List;
34+
import java.util.concurrent.ForkJoinPool;
3435

3536
import javax.annotation.Nonnull;
3637
import javax.annotation.Nullable;
@@ -94,6 +95,8 @@ public class BoxStoreBuilder {
9495
int maxReaders;
9596
boolean noReaderThreadLocals;
9697

98+
int executorServiceParallelism = ForkJoinPool.getCommonPoolParallelism();
99+
97100
int queryAttempts;
98101

99102
/** For DebugCursor. */
@@ -319,6 +322,15 @@ public BoxStoreBuilder noReaderThreadLocals() {
319322
return this;
320323
}
321324

325+
/**
326+
* Sets the maximum allowed level of parallelism allowed by executor service
327+
* used by BoxStore. The default value is equal to {@ref ForkJoinPool#getCommonPoolParallelism())}
328+
*/
329+
public BoxStoreBuilder executorServiceParallelism(int parallelism) {
330+
this.executorServiceParallelism = parallelism;
331+
return this;
332+
}
333+
322334
@Internal
323335
public void entity(EntityInfo<?> entityInfo) {
324336
entityInfoList.add(entityInfo);

objectbox-java/src/main/java/io/objectbox/internal/ObjectBoxThreadPool.java

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,84 @@
1717
package io.objectbox.internal;
1818

1919
import java.util.concurrent.Executors;
20-
import java.util.concurrent.SynchronousQueue;
21-
import java.util.concurrent.ThreadFactory;
22-
import java.util.concurrent.ThreadPoolExecutor;
20+
import java.util.List;
21+
import java.util.concurrent.AbstractExecutorService;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.ForkJoinPool;
24+
import java.util.concurrent.ForkJoinWorkerThread;
2325
import java.util.concurrent.TimeUnit;
24-
import java.util.concurrent.atomic.AtomicInteger;
2526

2627
import io.objectbox.BoxStore;
2728
import io.objectbox.annotation.apihint.Internal;
2829

2930
/**
30-
* Custom thread pool similar to {@link Executors#newCachedThreadPool()} with the following adjustments:
31+
* Custom executor service similar to {@link Executors#newWorkStealingPool()} with the following adjustments:
3132
* <ul>
32-
* <li>Release thread local resources ({@link BoxStore#closeThreadResources()})</li>
33-
* <li>Reduce keep-alive time for threads to 20 seconds</li>
34-
* <li>Uses a ThreadFactory to name threads like "ObjectBox-1-Thread-1"</li>
33+
* <li>Release thread local resources ({@link BoxStore#closeThreadResources()}) after task execution</li>
34+
* <li>Uses a custom thread factory to name threads like "ObjectBox-ForkJoinPool-1-Thread-1"</li>
3535
* </ul>
3636
*
3737
*/
3838
@Internal
39-
public class ObjectBoxThreadPool extends ThreadPoolExecutor {
39+
public final class ObjectBoxThreadPool extends AbstractExecutorService {
4040
private final BoxStore boxStore;
41+
private final ExecutorService executorImpl;
4142

42-
public ObjectBoxThreadPool(BoxStore boxStore) {
43-
super(0, Integer.MAX_VALUE, 20L, TimeUnit.SECONDS, new SynchronousQueue<>(),
44-
new ObjectBoxThreadFactory());
43+
public ObjectBoxThreadPool(BoxStore boxStore, int parallelism) {
4544
this.boxStore = boxStore;
45+
this.executorImpl = Executors.unconfigurableExecutorService(
46+
new ForkJoinPool(
47+
parallelism,
48+
pool -> {
49+
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
50+
// Priority and daemon status are inherited from calling thread; ensure to reset if required
51+
if (thread.getPriority() != Thread.NORM_PRIORITY) {
52+
thread.setPriority(Thread.NORM_PRIORITY);
53+
}
54+
if (thread.isDaemon()) {
55+
thread.setDaemon(false);
56+
}
57+
thread.setName("ObjectBox-" + thread.getName());
58+
return thread;
59+
},
60+
null,
61+
false));
4662
}
4763

64+
4865
@Override
49-
protected void afterExecute(Runnable runnable, Throwable throwable) {
50-
super.afterExecute(runnable, throwable);
51-
boxStore.closeThreadResources();
66+
public void shutdown() {
67+
executorImpl.shutdown();
5268
}
5369

54-
static class ObjectBoxThreadFactory implements ThreadFactory {
55-
private static final AtomicInteger POOL_COUNT = new AtomicInteger();
70+
@Override
71+
public List<Runnable> shutdownNow() {
72+
return executorImpl.shutdownNow();
73+
}
5674

57-
private final ThreadGroup group;
58-
private final String namePrefix = "ObjectBox-" + POOL_COUNT.incrementAndGet() + "-Thread-";
59-
private final AtomicInteger threadCount = new AtomicInteger();
75+
@Override
76+
public boolean isShutdown() {
77+
return executorImpl.isShutdown();
78+
}
6079

61-
ObjectBoxThreadFactory() {
62-
SecurityManager securityManager = System.getSecurityManager();
63-
group = (securityManager != null) ? securityManager.getThreadGroup() :
64-
Thread.currentThread().getThreadGroup();
65-
}
80+
@Override
81+
public boolean isTerminated() {
82+
return executorImpl.isTerminated();
83+
}
6684

67-
public Thread newThread(Runnable runnable) {
68-
String name = namePrefix + threadCount.incrementAndGet();
69-
Thread thread = new Thread(group, runnable, name);
85+
@Override
86+
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
87+
return executorImpl.awaitTermination(timeout, unit);
88+
}
7089

71-
// Priority and daemon status are inherited from calling thread; ensure to reset if required
72-
if (thread.getPriority() != Thread.NORM_PRIORITY) {
73-
thread.setPriority(Thread.NORM_PRIORITY);
74-
}
75-
if (thread.isDaemon()) {
76-
thread.setDaemon(false);
90+
@Override
91+
public void execute(Runnable command) {
92+
executorImpl.execute(() -> {
93+
try {
94+
command.run();
95+
} finally {
96+
boxStore.closeThreadResources();
7797
}
78-
return thread;
79-
}
98+
});
8099
}
81100
}

0 commit comments

Comments
 (0)