mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-08-05 08:23:06 +00:00
feature: Rewrite thread-local storage pattern (#2452)
This commit is contained in:
@@ -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<Integer> setter();
|
||||
|
||||
/**
|
||||
* Getter for our value.
|
||||
*
|
||||
* @return supplier
|
||||
*/
|
||||
protected abstract Supplier<Integer> getter();
|
||||
|
||||
private String getThreadName() {
|
||||
return Thread.currentThread().getName();
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> value;
|
||||
|
||||
/**
|
||||
* Removes the current thread's value for this thread-local variable.
|
||||
*/
|
||||
public void remove() {
|
||||
this.value.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Consumer<Integer> setter() {
|
||||
return value::set;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<Integer> getter() {
|
||||
return value::get;
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> setter() {
|
||||
return integer -> value = integer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<Integer> getter() {
|
||||
return () -> value;
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> lines = outContent.toString().lines().toList();
|
||||
Assertions.assertTrue(lines.stream()
|
||||
.allMatch(line -> line.endsWith(String.valueOf(initialValue))));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user