diff --git a/jena-core/src/main/java/org/apache/jena/sys/JenaSystem.java b/jena-core/src/main/java/org/apache/jena/sys/JenaSystem.java
index 09871f83b96..10aa64dc027 100644
--- a/jena-core/src/main/java/org/apache/jena/sys/JenaSystem.java
+++ b/jena-core/src/main/java/org/apache/jena/sys/JenaSystem.java
@@ -66,7 +66,8 @@ public JenaSystem() { }
* to avoid the risk of recursive initialization.
*/
public static boolean DEBUG_INIT = false ;
- private static volatile boolean initialized = false ;
+ private static volatile boolean IS_INITIALIZING = false;
+ private static volatile long ID_OF_INIT_THREAD = -1;
/** Output a debugging message if DEBUG_INIT is set */
public static void logLifecycle(String fmt, Object ...args) {
@@ -76,22 +77,34 @@ public static void logLifecycle(String fmt, Object ...args) {
System.err.println() ;
}
+ /**
+ * Initialization-on-demand holder idiom
+ * @see Initialization-on-demand holder idiom
+ *
+ */
+ private static class LazyHolder {
+ static final boolean initialized = initialize();
+ }
+
public static void init() {
- // Once jena is initialized, all calls are an immediate return.
- if ( initialized )
- return ;
- // Overlapping attempts to perform initialization will block on the synchronized.
- synchronized(JenaSystem.class) {
- if ( initialized )
- return ;
- setup();
- if ( DEBUG_INIT )
- singleton.debug(DEBUG_INIT);
- singleton.initialize();
- singleton.debug(false);
- // Last so overlapping initialization waits on the synchronized
- initialized = true;
+ if(IS_INITIALIZING && ID_OF_INIT_THREAD != Thread.currentThread().getId()) {
+ throw new ExceptionInInitializerError("Jena is already is being initialized by another thread. Please ensure that JenaSystem.init() is called before working with multiple threads.");
}
+
+ // Access the initialized flag to trigger class loading
+ boolean init = LazyHolder.initialized;
+ }
+
+ private static boolean initialize() {
+ IS_INITIALIZING = true;
+ ID_OF_INIT_THREAD = Thread.currentThread().getId();
+ setup();
+ if ( DEBUG_INIT )
+ singleton.debug(DEBUG_INIT);
+ singleton.initialize();
+ singleton.debug(false);
+ IS_INITIALIZING = false;
+ return true;
}
public static void shutdown() { singleton.shutdown(); }
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java b/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java
new file mode 100644
index 00000000000..9826dd48d10
--- /dev/null
+++ b/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sys;
+
+import org.apache.jena.sparql.exec.http.*;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses(
+ { TestJenaSystem.class
+ })
+
+public class TS_Sys { }
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java
new file mode 100644
index 00000000000..56221a8fee7
--- /dev/null
+++ b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jena.sys;
+
+import org.apache.jena.rdf.model.ModelFactory;
+import org.junit.Test;
+
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.HashSet;
+import java.util.concurrent.Executors;
+import java.util.stream.IntStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TestJenaSystem {
+
+ @Test
+ public void testInit() {
+ assertDoesNotThrow(() -> JenaSystem.init());
+ }
+
+ @Test
+ public void testInitParallel() {
+
+ // it is mandatory to init JenaSystem before running anything in parallel
+ JenaSystem.init();
+
+ var pool = Executors.newFixedThreadPool(8);
+
+ var futures = IntStream.range(0, 16)
+ .mapToObj(i -> pool.submit(() -> {
+ if (i % 2 == 0)
+ ModelFactory.createDefaultModel();
+ else
+ JenaSystem.init();
+
+ return i;
+ }))
+ .toList();
+
+ var intSet = new HashSet();
+ assertTimeoutPreemptively(
+ Duration.of(5, ChronoUnit.SECONDS),
+ () -> {
+ for (var future : futures) {
+ intSet.add(future.get());
+ }
+ });
+
+ assertEquals(16, intSet.size());
+ }
+}