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 442f39a0c..000000000 Binary files a/tls/etc/tls.png and /dev/null differ 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()); - } - } -}