diff --git a/src/main/java/com/networknt/schema/CachedSupplier.java b/src/main/java/com/networknt/schema/CachedSupplier.java index 109f36cbf..4411bdd05 100644 --- a/src/main/java/com/networknt/schema/CachedSupplier.java +++ b/src/main/java/com/networknt/schema/CachedSupplier.java @@ -23,8 +23,8 @@ * @param the type cached */ public class CachedSupplier implements Supplier { - private final Supplier delegate; - private T cache = null; + private volatile Supplier delegate; + private volatile T cache = null; public CachedSupplier(Supplier delegate) { this.delegate = delegate; @@ -32,10 +32,16 @@ public CachedSupplier(Supplier delegate) { @Override public T get() { - if (cache == null) { - cache = delegate.get(); + if (this.delegate == null) { + return this.cache; } - return cache; - } + synchronized(this) { + if (this.delegate != null) { + this.cache = this.delegate.get(); + this.delegate = null; + } + } + return this.cache; + } } diff --git a/src/test/java/com/networknt/schema/CachedSupplierTest.java b/src/test/java/com/networknt/schema/CachedSupplierTest.java new file mode 100644 index 000000000..f0b8cfab5 --- /dev/null +++ b/src/test/java/com/networknt/schema/CachedSupplierTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed 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 com.networknt.schema; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +/** + * Test for CachedSupplier. + */ +class CachedSupplierTest { + @Test + void nullValue() { + CachedSupplier supplier = new CachedSupplier<>(null); + assertNull(supplier.get()); + } + + @Test + void concurrency() throws Exception { + AtomicInteger value = new AtomicInteger(0); + + CachedSupplier supplier = new CachedSupplier<>(() -> { + return value.addAndGet(1); + }); + Exception[] instance = new Exception[1]; + CountDownLatch latch = new CountDownLatch(1); + List threads = new ArrayList<>(); + for (int i = 0; i < 50; ++i) { + Runnable runner = new Runnable() { + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + int result = supplier.get(); + assertEquals(1, result); + } catch(Exception e) { + synchronized(instance) { + instance[0] = e; + } + } + } + }; + Thread thread = new Thread(runner, "Thread" + i); + thread.start(); + threads.add(thread); + } + latch.countDown(); // Release the latch for threads to run concurrently + threads.forEach(t -> { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + if (instance[0] != null) { + throw instance[0]; + } + assertEquals(1, value.get()); + } +}