From 196233d587324e0a0f630ed720d6fb2dc7a87dac Mon Sep 17 00:00:00 2001 From: Eugene <48804404+Regyl@users.noreply.github.com> Date: Thu, 19 Jan 2023 21:50:59 +0300 Subject: [PATCH] feature: Rewrite thread-local storage pattern (#2452) --- pom.xml | 2 +- thread-local-storage/README.md | 247 ++++++++++++++++++ {tls => thread-local-storage}/pom.xml | 50 ++-- .../iluwatar/AbstractThreadLocalExample.java | 44 ++++ .../java/com/iluwatar/WithThreadLocal.java | 31 +++ .../java/com/iluwatar/WithoutThreadLocal.java | 24 ++ .../src/test/java/ThreadLocalTest.java | 68 +++++ tls/README.md | 19 -- tls/etc/tls.png | Bin 34509 -> 0 bytes tls/etc/tls.ucls | 79 ------ tls/etc/tls.urm.puml | 25 -- tls/src/main/java/com/iluwatar/tls/App.java | 143 ---------- .../com/iluwatar/tls/DateFormatCallable.java | 93 ------- .../main/java/com/iluwatar/tls/Result.java | 66 ----- .../test/java/com/iluwatar/tls/AppTest.java | 41 --- .../iluwatar/tls/DateFormatCallableTest.java | 147 ----------- ...FormatCallableTestIncorrectDateFormat.java | 121 --------- .../DateFormatCallableTestMultiThread.java | 165 ------------ 18 files changed, 432 insertions(+), 933 deletions(-) create mode 100644 thread-local-storage/README.md rename {tls => thread-local-storage}/pom.xml (63%) create mode 100644 thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java create mode 100644 thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java create mode 100644 thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java create mode 100644 thread-local-storage/src/test/java/ThreadLocalTest.java delete mode 100644 tls/README.md delete mode 100644 tls/etc/tls.png delete mode 100644 tls/etc/tls.ucls delete mode 100644 tls/etc/tls.urm.puml delete mode 100644 tls/src/main/java/com/iluwatar/tls/App.java delete mode 100644 tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java delete mode 100644 tls/src/main/java/com/iluwatar/tls/Result.java delete mode 100644 tls/src/test/java/com/iluwatar/tls/AppTest.java delete mode 100644 tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java delete mode 100644 tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java delete mode 100644 tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java diff --git a/pom.xml b/pom.xml index 4ffdf1eca..8c3d8fc74 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,6 @@ abstract-factory collecting-parameter monitor - tls builder factory-method prototype @@ -205,6 +204,7 @@ identity-map component context-object + thread-local-storage diff --git a/thread-local-storage/README.md b/thread-local-storage/README.md new file mode 100644 index 000000000..97506d58c --- /dev/null +++ b/thread-local-storage/README.md @@ -0,0 +1,247 @@ +--- +title: Thread-local storage +category: Concurrency +language: en +tag: +- Data access +--- + +## Intent + +Provide an ability to have a copy of a variable for each thread, making it thread-safe. + +## Explanation + +During code assembling compiler add _.tdata_ directive, +which means that variables below will store in different places from thread to thread. +This means changing variable in one thread won't affect the same variable in other thread. + +**Real world example** + +> On constructions each worker has its own shovel. When one of them broke his shovel, +> other still can continue work due to they have own instrument. + +**In plain words** + +>Each thread will have its own copy of variable. + +**Wikipedia says** + +>Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. + +**Programmatic Example** + +To define variable in thread-local storage you just need to put it into Java's ThreadLocal, e.g: + +```java +public class Main { + + private ThreadLocal stringThreadLocal = new ThreadLocal<>(); + + public void initialize(String value) { + this.stringThreadLocal.set(value); + } +} +``` + +Below we will contact a variable from two threads at a time to see, why we need it. +Main logic that show's current variable value is imposed in class AbstractThreadLocalExample, two implementations +differs only in the **value store approach**. + +Main class: + +```java +/** + * Class with main per-thread logic. + */ +public abstract class AbstractThreadLocalExample implements Runnable { + + private static final Random RND = new Random(); + + @Override + public void run() { + //Here we stop thread randomly + LockSupport.parkNanos(RND.nextInt(1_000_000_000, 2_000_000_000)); + + System.out.println(getCurrentThreadName() + ", before value changing: " + getter().get()); + setter().accept(RND.nextInt()); + } + + /** + * Setter for our value. + * + * @return consumer + */ + protected abstract Consumer setter(); + + /** + * Getter for our value. + * + * @return supplier + */ + protected abstract Supplier getter(); + + private String getCurrentThreadName() { + return Thread.currentThread().getName(); + } +} + +``` + +And two implementations. With ThreadLocal: +```java +/** + * Example of runnable with use of {@link ThreadLocal}. + */ +public class WithThreadLocal extends AbstractThreadLocalExample { + + private ThreadLocal value; + + public WithThreadLocal(ThreadLocal value) { + this.value = value; + } + + @Override + protected Consumer setter() { + return value::set; + } + + @Override + protected Supplier getter() { + return value::get; + } +} +``` + +And the class that stores value in one shared for all threads place: +```java +/** + * Example of runnable without usage of {@link ThreadLocal}. + */ +public class WithoutThreadLocal extends AbstractThreadLocalExample { + + private Integer value; + + public WithoutThreadLocal(Integer value) { + this.value = value; + } + + @Override + protected Consumer setter() { + return integer -> value = integer; + } + + @Override + protected Supplier getter() { + return () -> value; + } +} +``` + +Guess we want to run these classes. We will construct both implementations and invoke _run_ method. +So, we want to see initial value in console (thanks to System.out.println()). Let's take a look at tests. + +```java +public class ThreadLocalTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + + @Test + public void withoutThreadLocal() throws InterruptedException { + int initialValue = 1234567890; + + int threadSize = 2; + ExecutorService executor = Executors.newFixedThreadPool(threadSize); + + WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue); + for (int i = 0; i < threadSize; i++) { + executor.submit(threadLocal); + } + executor.awaitTermination(3, TimeUnit.SECONDS); + + List lines = outContent.toString().lines().toList(); + //Matches only first finished thread output, the second has changed by first thread value + Assertions.assertFalse(lines.stream() + .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); + } + + @Test + public void withThreadLocal() throws InterruptedException { + int initialValue = 1234567890; + + int threadSize = 2; + ExecutorService executor = Executors.newFixedThreadPool(threadSize); + + WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue)); + for (int i = 0; i < threadSize; i++) { + executor.submit(threadLocal); + } + + executor.awaitTermination(3, TimeUnit.SECONDS); + + List lines = outContent.toString().lines().toList(); + Assertions.assertTrue(lines.stream() + .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); + } +} +``` + +The output of test named withThreadLocal: +``` +pool-2-thread-2, before value changing: 1234567890 +pool-2-thread-1, before value changing: 1234567890 + +``` + +And the output of withoutThreadLocal: +``` +pool-1-thread-2, before value changing: 1234567890 +pool-1-thread-1, before value changing: 848843054 +``` +Where 1234567890 - is our initial value. We see, that in test _withoutThreadLocal_ +thread 2 got out from LockSupport#parkNanos earlier than the first and +change value in shared variable. + +## Class diagram + +```mermaid +classDiagram + class ThreadLocal + ThreadLocal : get() + ThreadLocal : initialValue() + ThreadLocal : remove() + ThreadLocal : set(T value) +``` + +## Applicability + +Use ThreadLocal when: + +- You need to run singleton with state in multiple threads +- You need to use not thread-safe classes in concurrency program + +## Tutorials +- [Baeldung](https://www.baeldung.com/java-threadlocal) + +## Known uses +- In java.lang.Thread during thread initialization +- In java.net.URL to prevent recursive provider lookups +- In org.junit.runners.BlockJUnit4ClassRunner to contain current rule +- In org.springframework:spring-web to store request context +- In org.apache.util.net.Nio2Endpoint to allow detecting if a completion handler completes inline +- In io.micrometer to avoid problems with not thread-safe NumberFormat + +## Credits +- [Usage cases](https://chao-tic.github.io/blog/2018/12/25/tls) +- [Implementation in Linux](https://uclibc.org/docs/tls.pdf) \ No newline at end of file diff --git a/tls/pom.xml b/thread-local-storage/pom.xml similarity index 63% rename from tls/pom.xml rename to thread-local-storage/pom.xml index b438e1b80..49726e281 100644 --- a/tls/pom.xml +++ b/thread-local-storage/pom.xml @@ -26,37 +26,21 @@ --> - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - tls - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.tls.App - - - - - - - - + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + thread-local-storage + + + + org.junit.jupiter + junit-jupiter-engine + test + + diff --git a/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java b/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java new file mode 100644 index 000000000..0ab20a24e --- /dev/null +++ b/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java @@ -0,0 +1,44 @@ +package com.iluwatar; + +import java.security.SecureRandom; +import java.util.concurrent.locks.LockSupport; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Class with main logic. + */ +public abstract class AbstractThreadLocalExample implements Runnable { + + private static final SecureRandom RND = new SecureRandom(); + + private static final Integer RANDOM_THREAD_PARK_START = 1_000_000_000; + private static final Integer RANDOM_THREAD_PARK_END = 2_000_000_000; + + @Override + public void run() { + long nanosToPark = RND.nextInt(RANDOM_THREAD_PARK_START, RANDOM_THREAD_PARK_END); + LockSupport.parkNanos(nanosToPark); + + System.out.println(getThreadName() + ", before value changing: " + getter().get()); + setter().accept(RND.nextInt()); + } + + /** + * Setter for our value. + * + * @return consumer + */ + protected abstract Consumer setter(); + + /** + * Getter for our value. + * + * @return supplier + */ + protected abstract Supplier getter(); + + private String getThreadName() { + return Thread.currentThread().getName(); + } +} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java new file mode 100644 index 000000000..d66440d83 --- /dev/null +++ b/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java @@ -0,0 +1,31 @@ +package com.iluwatar; + +import java.util.function.Consumer; +import java.util.function.Supplier; +import lombok.AllArgsConstructor; + +/** + * Example of runnable with use of {@link ThreadLocal}. + */ +@AllArgsConstructor +public class WithThreadLocal extends AbstractThreadLocalExample { + + private final ThreadLocal value; + + /** + * Removes the current thread's value for this thread-local variable. + */ + public void remove() { + this.value.remove(); + } + + @Override + protected Consumer setter() { + return value::set; + } + + @Override + protected Supplier getter() { + return value::get; + } +} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java new file mode 100644 index 000000000..8d60659ee --- /dev/null +++ b/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java @@ -0,0 +1,24 @@ +package com.iluwatar; + +import java.util.function.Consumer; +import java.util.function.Supplier; +import lombok.AllArgsConstructor; + +/** + * Example of runnable without usage of {@link ThreadLocal}. + */ +@AllArgsConstructor +public class WithoutThreadLocal extends AbstractThreadLocalExample { + + private Integer value; + + @Override + protected Consumer setter() { + return integer -> value = integer; + } + + @Override + protected Supplier getter() { + return () -> value; + } +} diff --git a/thread-local-storage/src/test/java/ThreadLocalTest.java b/thread-local-storage/src/test/java/ThreadLocalTest.java new file mode 100644 index 000000000..7c069b227 --- /dev/null +++ b/thread-local-storage/src/test/java/ThreadLocalTest.java @@ -0,0 +1,68 @@ +import com.iluwatar.WithThreadLocal; +import com.iluwatar.WithoutThreadLocal; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class ThreadLocalTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + + @Test + public void withoutThreadLocal() throws InterruptedException { + int initialValue = 1234567890; + + int threadSize = 2; + ExecutorService executor = Executors.newFixedThreadPool(threadSize); + + WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue); + for (int i = 0; i < threadSize; i++) { + executor.submit(threadLocal); + } + executor.awaitTermination(3, TimeUnit.SECONDS); + + List lines = outContent.toString().lines().toList(); + //Matches only first thread, the second has changed by first thread value + Assertions.assertFalse(lines.stream() + .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); + } + + @Test + public void withThreadLocal() throws InterruptedException { + int initialValue = 1234567890; + + int threadSize = 2; + ExecutorService executor = Executors.newFixedThreadPool(threadSize); + + WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue)); + for (int i = 0; i < threadSize; i++) { + executor.submit(threadLocal); + } + + executor.awaitTermination(3, TimeUnit.SECONDS); + threadLocal.remove(); + + List lines = outContent.toString().lines().toList(); + Assertions.assertTrue(lines.stream() + .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); + } +} diff --git a/tls/README.md b/tls/README.md deleted file mode 100644 index d36c1829a..000000000 --- a/tls/README.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Thread Local Storage -category: Idiom -language: en -tag: - - Performance ---- - -## Intent -Securing variables global to a thread against being spoiled by other threads. That is needed if you use class variables or static variables in your Callable object or Runnable object that are not read-only. - -## Class diagram -![alt text](./etc/tls.png "Thread Local Storage") - -## Applicability -Use the Thread Local Storage in any of the following situations - -* When you use class variables in your Callable / Runnable object that are not read-only and you use the same Callable instance in more than one thread running in parallel. -* When you use static variables in your Callable / Runnable object that are not read-only and more than one instances of the Callable / Runnable may run in parallel threads. diff --git a/tls/etc/tls.png b/tls/etc/tls.png deleted file mode 100644 index 442f39a0c730b6db350e1a2e0bffba1a2a60e8da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34509 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 Su=8Tx-<3k&DUf*V`+op$;IV=L diff --git a/tls/etc/tls.ucls b/tls/etc/tls.ucls deleted file mode 100644 index bd238346d..000000000 --- a/tls/etc/tls.ucls +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tls/etc/tls.urm.puml b/tls/etc/tls.urm.puml deleted file mode 100644 index 12bcea064..000000000 --- a/tls/etc/tls.urm.puml +++ /dev/null @@ -1,25 +0,0 @@ -@startuml -package com.iluwatar.tls { - class App { - - LOGGER : Logger {static} - + App() - + main(args : String[]) {static} - - printAndCountDates(res : Result) : int {static} - - printAndCountExceptions(res : Result) : int {static} - } - class DateFormatCallable { - - LOGGER : Logger {static} - - dateValue : String - - df : ThreadLocal - + DateFormatCallable(inDateFormat : String, inDateValue : String) - + call() : Result - } - class Result { - - dateList : List - - exceptionList : List - + Result() - + getDateList() : List - + getExceptionList() : List - } -} -@enduml \ No newline at end of file diff --git a/tls/src/main/java/com/iluwatar/tls/App.java b/tls/src/main/java/com/iluwatar/tls/App.java deleted file mode 100644 index 5932b72b5..000000000 --- a/tls/src/main/java/com/iluwatar/tls/App.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import java.util.Calendar; -import java.util.concurrent.Executors; -import lombok.extern.slf4j.Slf4j; - -/** - * ThreadLocal pattern - * - *

This App shows how to create an isolated space per each thread. In this example the usage of - * SimpleDateFormat is made to be thread-safe. This is an example of the ThreadLocal pattern. - * - *

By applying the ThreadLocal pattern you can keep track of application instances or locale - * settings throughout the handling of a request. The ThreadLocal class works like a static - * variable, with the exception that it is only bound to the current thread! This allows us to use - * static variables in a thread-safe way. - * - *

In Java, thread-local variables are implemented by the ThreadLocal class object. ThreadLocal - * holds a variable of type T, which is accessible via get/set methods. - * - *

SimpleDateFormat is one of the basic Java classes and is not thread-safe. If you do not - * isolate the instance of SimpleDateFormat per each thread then problems arise. - * - *

App converts the String date value 15/12/2015 to the Date format using the Java class - * SimpleDateFormat. It does this 20 times using 4 threads, each doing it 5 times. With the usage of - * as ThreadLocal in DateFormatCallable everything runs well. But if you comment out the ThreadLocal - * variant (marked with "//TLTL") and comment in the non ThreadLocal variant (marked with - * "//NTLNTL") you can see what will happen without the ThreadLocal. Most likely you will get - * incorrect date values and / or exceptions. - * - *

This example clearly show what will happen when using non thread-safe classes in a thread. In - * real life this may happen one in of 1.000 or 10.000 conversions and those are really hard to find - * errors. - * - * @author Thomas Bauer, 2017 - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - var counterDateValues = 0; - var counterExceptions = 0; - - // Create a callable - var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); - // start 4 threads, each using the same Callable instance - var executor = Executors.newCachedThreadPool(); - - var futureResult1 = executor.submit(callableDf); - var futureResult2 = executor.submit(callableDf); - var futureResult3 = executor.submit(callableDf); - var futureResult4 = executor.submit(callableDf); - try { - var result = new Result[4]; - result[0] = futureResult1.get(); - result[1] = futureResult2.get(); - result[2] = futureResult3.get(); - result[3] = futureResult4.get(); - - // Print results of thread executions (converted dates and raised exceptions) - // and count them - for (var value : result) { - counterDateValues = counterDateValues + printAndCountDates(value); - counterExceptions = counterExceptions + printAndCountExceptions(value); - } - - // a correct run should deliver 20 times 15.12.2015 - // and a correct run shouldn't deliver any exception - LOGGER.info("The List dateList contains " + counterDateValues + " date values"); - LOGGER.info("The List exceptionList contains " + counterExceptions + " exceptions"); - - } catch (Exception e) { - LOGGER.info("Abnormal end of program. Program throws exception: " + e); - } - executor.shutdown(); - } - - /** - * Print result (date values) of a thread execution and count dates. - * - * @param res contains results of a thread execution - */ - private static int printAndCountDates(Result res) { - // a correct run should deliver 5 times 15.12.2015 per each thread - var counter = 0; - for (var dt : res.getDateList()) { - counter++; - var cal = Calendar.getInstance(); - cal.setTime(dt); - // Formatted output of the date value: DD.MM.YYYY - LOGGER.info(cal.get(Calendar.DAY_OF_MONTH) + "." - + cal.get(Calendar.MONTH) + "." - + cal.get(Calendar.YEAR) - ); - } - return counter; - } - - /** - * Print result (exceptions) of a thread execution and count exceptions. - * - * @param res contains results of a thread execution - * @return number of dates - */ - private static int printAndCountExceptions(Result res) { - // a correct run shouldn't deliver any exception - var counter = 0; - for (var ex : res.getExceptionList()) { - counter++; - LOGGER.info(ex); - } - return counter; - } -} diff --git a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java deleted file mode 100644 index e6d7ef9b4..000000000 --- a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.concurrent.Callable; -import java.util.stream.IntStream; -import lombok.extern.slf4j.Slf4j; - -/** - * DateFormatCallable converts string dates to a date format using SimpleDateFormat. The date format - * and the date value will be passed to the Callable by the constructor. The constructor creates a - * instance of SimpleDateFormat and stores it in a ThreadLocal class variable. For the complete - * description of the example see {@link App}. - * - *

You can comment out the code marked with //TLTL and comment in the code marked //NTLNTL. Then - * you can see what will happen if you do not use the ThreadLocal. For details see the description - * of {@link App} - * - * @author Thomas Bauer, 2017 - */ -@Slf4j -public class DateFormatCallable implements Callable { - - // class variables (members) - private final ThreadLocal df; //TLTL - // private DateFormat df; //NTLNTL - - private final String dateValue; // for dateValue Thread Local not needed - - - /** - * The date format and the date value are passed to the constructor. - * - * @param inDateFormat string date format string, e.g. "dd/MM/yyyy" - * @param inDateValue string date value, e.g. "21/06/2016" - */ - public DateFormatCallable(String inDateFormat, String inDateValue) { - final var idf = inDateFormat; //TLTL - this.df = ThreadLocal.withInitial(() -> { //TLTL - return new SimpleDateFormat(idf); //TLTL - }); //TLTL - // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL - this.dateValue = inDateValue; - } - - @Override - public Result call() { - LOGGER.info(Thread.currentThread() + " started executing..."); - var result = new Result(); - - // Convert date value to date 5 times - IntStream.rangeClosed(1, 5).forEach(i -> { - try { - // this is the statement where it is important to have the - // instance of SimpleDateFormat locally - // Create the date value and store it in dateList - result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL - // result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL - } catch (Exception e) { - // write the Exception to a list and continue work - result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); - } - }); - - LOGGER.info("{} finished processing part of the thread", Thread.currentThread()); - - return result; - } -} diff --git a/tls/src/main/java/com/iluwatar/tls/Result.java b/tls/src/main/java/com/iluwatar/tls/Result.java deleted file mode 100644 index 48e30a525..000000000 --- a/tls/src/main/java/com/iluwatar/tls/Result.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -/* - * Fiducia IT AG, All rights reserved. Use is subject to license terms. - */ - -package com.iluwatar.tls; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * Result object that will be returned by the Callable {@link DateFormatCallable} used in {@link - * App}. - * - * @author Thomas Bauer, 2017 - */ -public class Result { - // A list to collect the date values created in one thread - private final List dateList = new ArrayList<>(); - - // A list to collect Exceptions thrown in one threads (should be none in - // this example) - private final List exceptionList = new ArrayList<>(); - - /** - * Get list of date values collected within a thread execution. - * - * @return List of date values collected within an thread execution - */ - public List getDateList() { - return dateList; - } - - /** - * Get list of exceptions thrown within a thread execution. - * - * @return List of exceptions thrown within an thread execution - */ - public List getExceptionList() { - return exceptionList; - } -} diff --git a/tls/src/test/java/com/iluwatar/tls/AppTest.java b/tls/src/test/java/com/iluwatar/tls/AppTest.java deleted file mode 100644 index fa53ee34d..000000000 --- a/tls/src/test/java/com/iluwatar/tls/AppTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests that thread local storage example runs without errors. - * - * @author Thomas Bauer, January 2017 - */ -class AppTest { - @Test - void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } -} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java deleted file mode 100644 index 3bd9497be..000000000 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Test of the Callable - *

- * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency - * situation) - *

- * After a successful run 5 date values should be in the result object. All dates should have the - * same value (15.11.2015). To avoid problems with time zone not the date instances themselves are - * compared by the test. For the test the dates are converted into string format DD.MM.YYY - *

- * Additionally the number of list entries are tested for both the list with the date values and the - * list with the exceptions - * - * @author Thomas Bauer, January 2017 - */ -class DateFormatCallableTest { - - // Class variables used in setup() have to be static because setup() has to be static - /** - * Result object given back by DateFormatCallable -- Array with converted date values -- Array - * with thrown exceptions - */ - private static Result result; - - /** - * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() - * method - */ - private static List createdDateValues = new ArrayList<>(); - - /** - * Expected number of date values in the date value list created by the run of DateFormatRunnalbe - */ - private final int expectedCounterDateValues = 5; - - /** - * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. - */ - private final int expectedCounterExceptions = 0; - - /** - * Expected content of the list containing the date values created by the run of - * DateFormatRunnalbe - */ - private final List expectedDateValues = - List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); - - /** - * Run Callable and prepare results for usage in the test methods - */ - @BeforeAll - public static void setup() { - // Create a callable - var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); - // start thread using the Callable instance - var executor = Executors.newCachedThreadPool(); - var futureResult = executor.submit(callableDf); - try { - result = futureResult.get(); - createdDateValues = convertDatesToString(result); - } catch (Exception e) { - fail("Setup failed: " + e); - } - executor.shutdown(); - } - - private static List convertDatesToString(Result res) { - // Format date value as DD.MM.YYYY - if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { - return null; - } - var returnList = new ArrayList(); - - for (var dt : res.getDateList()) { - var cal = Calendar.getInstance(); - cal.setTime(dt); - returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." - + cal.get(Calendar.MONTH) + "." - + cal.get(Calendar.YEAR) - ); - } - return returnList; - } - - /** - * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times - * 15.12.2015 - */ - @Test - void testDateValues() { - assertEquals(expectedDateValues, createdDateValues); - } - - /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should - * deliver 5 date values - */ - @Test - void testCounterDateValues() { - assertEquals(expectedCounterDateValues, result.getDateList().size()); - } - - /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should - * deliver no exceptions - */ - @Test - void testCounterExceptions() { - assertEquals(expectedCounterExceptions, result.getExceptionList().size()); - } -} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java deleted file mode 100644 index 69489d4d0..000000000 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.List; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Test of the Callable - *

- * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency - * situation) - *

- * An incorrect formatted date is passed to the Callable After a successful run 0 date values and 5 - * exceptions should be in the result object. - * - * @author Thomas Bauer, January 2017 - */ -public class DateFormatCallableTestIncorrectDateFormat { - - // Class variables used in setup() have to be static because setup() has to be static - /** - * Result object given back by DateFormatCallable -- Array with converted date values -- Array - * with thrown exceptions - */ - private static Result result; - - /** - * Expected number of date values in the date value list created by the run of DateFormatRunnalbe - */ - private final int expectedCounterDateValues = 0; - - /** - * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. - */ - private final int expectedCounterExceptions = 5; - - /** - * Expected content of the list containing the exceptions created by the run of - * DateFormatRunnalbe - */ - private final List expectedExceptions = List.of( - "class java.text.ParseException: Unparseable date: \"15.12.2015\"", - "class java.text.ParseException: Unparseable date: \"15.12.2015\"", - "class java.text.ParseException: Unparseable date: \"15.12.2015\"", - "class java.text.ParseException: Unparseable date: \"15.12.2015\"", - "class java.text.ParseException: Unparseable date: \"15.12.2015\"" - ); - - /** - * Run Callable and prepare results for usage in the test methods - */ - @BeforeAll - public static void setup() { - // Create a callable. Pass a string date value not matching the format string - var callableDf = new DateFormatCallable("dd/MM/yyyy", "15.12.2015"); - // start thread using the Callable instance - var executor = Executors.newCachedThreadPool(); - var futureResult = executor.submit(callableDf); - try { - result = futureResult.get(); - } catch (Exception e) { - fail("Setup failed: " + e); - } - executor.shutdown(); - } - - /** - * Test Exceptions after the run of DateFormatRunnalbe. A correct run should deliver 5 times the - * same exception - */ - @Test - void testExceptions() { - assertEquals(expectedExceptions, result.getExceptionList()); - } - - /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should - * deliver no date values - */ - @Test - void testCounterDateValues() { - assertEquals(expectedCounterDateValues, result.getDateList().size()); - } - - /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should - * deliver 5 exceptions - */ - @Test - void testCounterExceptions() { - assertEquals(expectedCounterExceptions, result.getExceptionList().size()); - } -} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java deleted file mode 100644 index f8b7f952a..000000000 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.tls; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * Test of the Callable - *

- * In this test {@link DateFormatCallable} is used by 4 threads in parallel - *

- * After a successful run 5 date values should be in the result object of each thread. All dates - * should have the same value (15.11.2015). To avoid problems with time zone not the date instances - * themselves are compared by the test. For the test the dates are converted into string format - * DD.MM.YYY - *

- * Additionally the number of list entries are tested for both the list with the date values and the - * list with the exceptions - * - * @author Thomas Bauer, January 2017 - */ -public class DateFormatCallableTestMultiThread { - - // Class variables used in setup() have to be static because setup() has to be static - /** - * Result object given back by DateFormatCallable, one for each thread -- Array with converted - * date values -- Array with thrown exceptions - */ - private static final Result[] result = new Result[4]; - - /** - * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() - * method - */ - @SuppressWarnings("serial") - private static class StringArrayList extends ArrayList { - /* nothing needed here */ - } - - private static final List[] createdDateValues = new StringArrayList[4]; - - /** - * Expected number of date values in the date value list created by each thread - */ - private final int expectedCounterDateValues = 5; - - /** - * Expected number of exceptions in the exception list created by each thread - */ - private final int expectedCounterExceptions = 0; - - /** - * Expected content of the list containing the date values created by each thread - */ - private final List expectedDateValues = - List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); - - /** - * Run Callable and prepare results for usage in the test methods - */ - @BeforeAll - public static void setup() { - // Create a callable - var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); - // start thread using the Callable instance - var executor = Executors.newCachedThreadPool(); - var futureResult1 = executor.submit(callableDf); - var futureResult2 = executor.submit(callableDf); - var futureResult3 = executor.submit(callableDf); - var futureResult4 = executor.submit(callableDf); - try { - result[0] = futureResult1.get(); - result[1] = futureResult2.get(); - result[2] = futureResult3.get(); - result[3] = futureResult4.get(); - for (var i = 0; i < result.length; i++) { - createdDateValues[i] = convertDatesToString(result[i]); - } - } catch (Exception e) { - fail("Setup failed: " + e); - } - executor.shutdown(); - } - - private static List convertDatesToString(Result res) { - // Format date value as DD.MM.YYYY - if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { - return null; - } - var returnList = new StringArrayList(); - - for (var dt : res.getDateList()) { - var cal = Calendar.getInstance(); - cal.setTime(dt); - returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." - + cal.get(Calendar.MONTH) + "." - + cal.get(Calendar.YEAR) - ); - } - return returnList; - } - - /** - * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times - * 15.12.2015 by each thread - */ - @Test - void testDateValues() { - for (var createdDateValue : createdDateValues) { - assertEquals(expectedDateValues, createdDateValue); - } - } - - /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should - * deliver 5 date values by each thread - */ - @Test - void testCounterDateValues() { - for (var value : result) { - assertEquals(expectedCounterDateValues, value.getDateList().size()); - } - } - - /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should - * deliver no exceptions - */ - @Test - void testCounterExceptions() { - for (var value : result) { - assertEquals(expectedCounterExceptions, value.getExceptionList().size()); - } - } -}