ํฐ์คํ ๋ฆฌ ๋ทฐ
ํด๋น ํฌ์คํ ์ ๋น๋๊ธฐ๋ก ๋์ํ๋ ์คํ๋ง ํ๋ก์ ํธ ํ๊ฒฝ์์ ์์ ํ Context ์ ํ ์ ๋ต์ ๋์ ํ๊ธฐ ์ํ ThreadLocal๊ณผ InheritableThreadLocal ๊ณ ์ฐฐ์ ๋ํ ๋ด์ฉ์ ๋๋ค.
1. ๋ฌธ์ ๋ฐฐ๊ฒฝ
- ์์คํ ์์ ์ฌ์ฉ์ ์์ฒญ ์ ๊ณ ๊ฐ ์ ๋ณด๋ฅผ ThreadLocal์ ์ ์ฅํ๊ณ , ์์ฒญ ์ฒ๋ฆฌ ์ ์ฒด ๊ณผ์ ์์ ์ผ๊ด๋๊ฒ ์ ์งํ๊ณ ์ ํจ
- ํ์ง๋ง, ๋น๋๊ธฐ ์์ ๋๋ ์ค๋ ๋ ํ ํ๊ฒฝ์์๋ ThreadLocal์ ํ๊ณ๋ก ์ธํด ๊ณ ๊ฐ ์ ๋ณด๊ฐ ์ ํ๋์ง ์๊ฑฐ๋, ๋์/์์ ๋ฑ์ ๋ฌธ์ ๊ฐ ๋ฐ์ ๊ฐ๋ฅ.
2. InheritableThreadLocal ๋์ ๊ณ ๋ ค ๋ฐ ๋ฌธ์ ๋ถ์
InheritableThreadLocal์ ์์ ์ค๋ ๋๊ฐ ์์ฑ๋ ๋ ๋ถ๋ชจ ์ค๋ ๋์ ๊ฐ์ ์๋์ผ๋ก ๋ณต์ฌํ๋ค๋ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
2.1. InheritableThreadLocal์ ๋ด๋ถ ๊ตฌ์กฐ
public class AccountContext {
public static final InheritableThreadLocal<AccountInfo> accountThreadLocal = new InheritableThreadLocal<>();
}
Thread-Main.inheritableThreadLocals
โณ key: accountThreadLocal → value: AccountInfo@0x123
Thread-Sub.inheritableThreadLocals
โณ key: accountThreadLocal → value: AccountInfo@0x123
- userThreadLocal์ static์ผ๋ก ์ ์ญ 1๊ฐ
- ๊ฐ ์ค๋ ๋๋ ์์ ์ ThreadLocalMap์ ๊ฐ์ง
- ThreadLocalMap์ key๋ ๋์ผํ userThreadLocal ์ธ์คํด์ค
- ์ด ๊ตฌ์กฐ๋ก ์ธํด, ๊ฐ์ ํค๋ก ๋ค๋ฅธ ์ค๋ ๋์ ๊ฐ์ ๊ตฌ๋ถํด์ ์ ๊ทผ ๊ฐ๋ฅ
2.2. ๋ฌธ์ ์ ๋ฐ ํ๊ณ
1) ๊ฐ์ ๋ณต์ฌ๋์ง๋ง ์ฐธ์กฐ๊ฐ ๊ณต์ ๋จ
- mainThread → subThread๋ก ์ ํ ์, ๊ฐ์ ๋ณต์ฌ๋์ง๋ง ๊ฐ์ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๊ฐ์ง.
- ์ฆ, ์์ ์ค๋ ๋์์ ๊ฐ์ฒด ๋ด๋ถ ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด, ๋ถ๋ชจ์๊ฒ๋ ์ํฅ์ ๋ฏธ์นจ.
ThreadLocalMap
- mainThread : AccountInfo@0x123
- subThread : AccountInfo@0x123
2) ๊ฐ์ฒด ์์ฒด๋ฅผ ์๋ก ์์ฑํ๋ฉด ๋ถ๋ชจ/์์ ๊ฐ์ด ๋ถ๋ฆฌ
- ์์ ์ค๋ ๋์์ new AccountInfo(...)๋ก ์๋ก์ด ๊ฐ์ฒด๋ฅผ setํ๋ฉด ๋ถ๋ชจ์๋ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ถ๋ฆฌ๋จ.
- ๊ทธ๋ฌ๋ ์ด๋ฅผ ์ํด์ ๊ฐ๋ฐ์๊ฐ ๋ช ์์ ์ผ๋ก ๊ฐ์ฒด๋ฅผ ์๋ก ์์ฑํด์ผ ํ๋ฉฐ, ์๋ ์ ํ์ ์๋ฏธ๊ฐ ํด์๋จ.
3) ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ฌธ์
- ThreadLocalMap์ ๊ฐ ์ค๋ ๋๋ง๋ค ์กด์ฌ
- mainThread์์ ThreadLocal.remove() ํด๋ ์์ ์ค๋ ๋์ ๊ฐ์ ์ฌ์ ํ ๋จ์ ์์.
- ์์ ์ค๋ ๋์์ ์ง์ removeํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์ ์ํ ์กด์ฌ
4) ์ค๋ ๋ ํ ํ๊ฒฝ์์๋ ๊ฐ์ด ์ ํ๋์ง ์์
- InheritableThreadLocal์ ์ค๋ ๋ ์์ฑ ์์ ์๋ง ๊ฐ ๋ณต์ฌ๋จ.
- ์ค๋ ๋ ํ์ ๊ธฐ์กด ์ค๋ ๋๋ฅผ ์ฌ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ์๋ก ์์ฑ๋์ง ์์ผ๋ฉด ๊ฐ์ด ์ ํ๋์ง ์์.
- ์ผ๋ฐ์ ์ผ๋ก ๋น๋๊ธฐ ๋์์ ์ํด์ ์ค๋ ๋ ํ์ ๋ง๋ค์ด์ ์ฌ์ฉ.
3. InheritableThreadLocal ๋ณด์ ์๋ ๋ฐ ํ๊ณ
3.1. ๋ถ๋ณ ๊ฐ์ฒด ์ฌ์ฉ
- ๋ด๋ถ ์ํ๊ฐ ๋ฐ๋์ง ์๋๋ก ์ค๊ณ
- ์ด๋ฏธ ๊ฐ์ด ๋ณต์ฌ๋์ด ๋ค์ด ์์ผ๋ฉด ์๋ก ์์ฑ ๋ถ๊ฐ๋ฅํ๋๋ก ์ฒ๋ฆฌ
- setter ๋ชจ๋ ์ ๊ฑฐ, final ํ๋ ์ฌ์ฉ
3.2. ์ ํ ์ ์๋ ๋ณต์ฌ
- ์์ ์ค๋ ๋ ์์ฑ ์, ์๋์ผ๋ก ๋ณต์ฌ๋ ๊ฐ์ฒด๋ฅผ ์ค์
3.3. ๋ช ์์ remove() ์ฒ๋ฆฌ
- ์์ ์ค๋ ๋๊ฐ ์ข ๋ฃ๋๊ธฐ ์ ์ ThreadLocal.remove() ํธ์ถ๋ก ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง
ํ์ง๋ง, ํด๋น ๋ฐฉ๋ฒ๋ค์ ๊ฒฐ๊ตญ ๋ผ์ดํ ์ฌ์ดํด์ ์ง์ ๊ด๋ฆฌํ ์ ์์ผ๋, ์๋์ ๊ฐ์ ํ๊ณ๊ฐ ์ฌ์ ํ ๋จ์์์ต๋๋ค.
- ์๋ ์ ํ์ ์๋ฏธ๊ฐ ํด์๋จ
- ์๋์น ์์ ๊ฐ์ ๋ณต์ฌ์ ๋ํด์ ๋ช ์์ remove ์ฒ๋ฆฌ๊ฐ ์ด๋ ค์
๋ฐ๋ผ์, ์ค๋ ๋ ํ ์ฌ์ฉ ํ๊ฒฝ์์ InheritableThreadLocal ์ฌ์ฉ ๋ง๊ณ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด์์ต๋๋ค.
4. ๊ฒฐ๋ก : TaskDecorator + ์ผ๋ฐ ThreadLocal
4.1. TaskDecorator๋ฅผ ํ์ฉํ์ฌ ThreadLocal๊ฐ์ ์ ์ง
- ์ผ๋ฐ ThreadLocal ์ฌ์ฉ
- TaskDecorator๋ฅผ ํตํด ๋ถ๋ชจ ์ค๋ ๋์ ๊ฐ์ ์๋์ผ๋ก ๋ณต์ฌ
- ์์ ์ข ๋ฃ ํ finally์์ remove()๋ฅผ ์ํํ์ฌ GC ์ ๋
public class AccountContextTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
AccountInfo context = AccountContext.get(); // ThreadLocal์์ ๊ฐ ๋ณต์ฌ
return () -> {
try {
AccountContext.set(context); // ๋ณต์ฌํ ๊ฐ ์ค์
runnable.run(); // ์๋ ์์
์คํ
} finally {
AccountContext.clear(); // ๋์ ๋ฐฉ์ง๋ฅผ ์ํด remove()
}
};
}
}
4.2. ThreadPool์ด ํด๋น TaskDecorator๋ฅผ ์ฌ์ฉํ๋๋ก ๋ฑ๋ก
@Configuration
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2); // ๊ธฐ๋ณธ ์ฐ๋ ๋ ์
executor.setMaxPoolSize(8); // ์ต๋ ์ฐ๋ ๋ ์
executor.setQueueCapacity(100); // ๋๊ธฐ ํ ํฌ๊ธฐ
executor.setThreadNamePrefix("async-thread-"); // ์ฐ๋ ๋ ์ด๋ฆ prefix
executor.setTaskDecorator(new AccountContextTaskDecorator()); // ์ปค์คํ
TaskDecorator ๋ฑ๋ก
executor.initialize();
return executor;
}
}
๊ฒฐ๊ณผ์ ์ผ๋ก, ์๋์ ๊ฐ์ด ์์ ํ๊ฒ ๊ณ ๊ฐ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
- ๋ช ํํ๊ฒ Context ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌ
- ๋น๋๊ธฐ ํ๊ฒฝ์์๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ ํ ๊ฐ๋ฅ
- GC ๋์๋ฅผ ๋ฐฉ์ง
5. ํ ์คํธ ์ฝ๋
ํด๋น ์ฝ๋๋ ์์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋์ถํ๊ธฐ ์ํด ์งํํ ํ ์คํธ ์ฝ๋์ ๋๋ค.
class AccountContextTest {
static class AccountInfo {
private String name;
public AccountInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private static final InheritableThreadLocal<AccountInfo> accountThreadLocal = new InheritableThreadLocal<>();
@DisplayName("์์์ด ํ๋๋ฅผ ๋ณ๊ฒฝํ๋ฉด ๋ถ๋ชจ์ ํ๋๋ ๊ฐ์ด ๋ณ๊ฒฝ๋จ")
@Test
void testInheritableThreadLocal_change_field_same() throws InterruptedException {
// mainThread์์ ๊ฐ ์
ํ
AccountInfo accountInfo = new AccountInfo("main");
accountThreadLocal.set(accountInfo);
System.out.println("[" + Thread.currentThread().getName() + "] main์ด ์ค์ => " + accountThreadLocal.get().getName());
// subThread์์ ํ๋ ๊ฐ ๋ณ๊ฒฝ
Thread sub = new Thread(() -> {
AccountInfo inherited = accountThreadLocal.get();// ๋ถ๋ชจ๋ก๋ถํฐ ์ฐธ์กฐ ๋ณต์ฌ๋จ
System.out.println("[" + Thread.currentThread().getName() + "] main์์ ๋ฌผ๋ ค๋ฐ์ => " + accountThreadLocal.get().getName());
inherited.setName("sub");
System.out.println("[" + Thread.currentThread().getName() + "] sub๊ฐ ๋ณ๊ฒฝํจ => " + accountThreadLocal.get().getName());
});
sub.start();
sub.join();// subThread ์ข
๋ฃ ๋๊ธฐ// mainThread์์ ๋ค์ ๊ฐ ์กฐํ (์์์ด ๋ณ๊ฒฝํ ๊ฐ ํ์ธ)
System.out.println("[" + Thread.currentThread().getName() + "] main๋ ๋ณ๊ฒฝ๋จ => " + accountThreadLocal.get().getName());
// ๊ฒฐ๊ณผ: ๋ณ๊ฒฝ๋์ด ์์ด์ผ ํจ
assertThat(accountThreadLocal.get().getName()).isEqualTo("sub");
}
@DisplayName("์์์ด ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์์๋ง ๋ณ๊ฒฝ๋จ")
@Test
void testInheritableThreadLocal_change_object_diff() throws InterruptedException {
// mainThread์์ ๊ฐ ์
ํ
AccountInfo accountInfo = new AccountInfo("main");
accountThreadLocal.set(accountInfo);
System.out.println("[" + Thread.currentThread().getName() + "] main์ด ์ค์ => " + accountThreadLocal.get().getName());
// subThread์์ ๊ฐ์ฒด ๋ณ๊ฒฝ
Thread sub = new Thread(() -> {
System.out.println("[" + Thread.currentThread().getName() + "] main์์ ๋ฌผ๋ ค๋ฐ์ => " + accountThreadLocal.get().getName());
AccountInfo subAccountInfo = new AccountInfo("sub");
accountThreadLocal.set(subAccountInfo);
System.out.println("[" + Thread.currentThread().getName() + "] sub์ ์๋ก์ด๊ฑฐ ๋ฃ์ => " + accountThreadLocal.get().getName());
});
sub.start();
sub.join();
// mainThread์์ ํ์ธ
System.out.println("[" + Thread.currentThread().getName() + "] main์ ๊ทธ๋๋ก => " + accountThreadLocal.get().getName());
// ๊ฒฐ๊ณผ: ๋ณ๊ฒฝX
assertThat(accountThreadLocal.get().getName()).isEqualTo("main");
}
/**
* accountThreadLocal๊ณผ ์ค๋ ๋ ํ
*@throwsInterruptedException */
@DisplayName("์ค๋ ๋ ์ ํ ํ main์ค๋ ๋ ๋จผ์ ์ข
๋ฃ์ ThreadLocal๊ฐ์ด ์ด์์์")
@Test
void testInheritableThreadLocal_main_terminate_sub_alive() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
// MainThread์์ ์ค์
accountThreadLocal.set(new AccountInfo("main"));
System.out.println("[" + Thread.currentThread().getName() + "] set name = main");
// ๋น๋๊ธฐ ์คํ (InheritableThreadLocal์ ์ค๋ ๋ ์์ฑ ์๋ง ๋ณต์ฌ๋๋ฏ๋ก ์ฃผ์)
executor.submit(() -> {
// ์ด ์ฐ๋ ๋๋ ํ์์ ์ฌ์ฌ์ฉ๋๋ฏ๋ก InheritableThreadLocal์ด null์ผ ์ ์์
AccountInfo accountInfo = accountThreadLocal.get();
System.out.println("[" + Thread.currentThread().getName() + "] inherited? " + (accountInfo != null ? accountInfo.getName() : "null"));
try {
try {
Thread.sleep(3000);// ์ค๋ ๊ฑธ๋ฆฌ๋ ์์
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("[" + Thread.currentThread().getName() + "] ์์
์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get().getName());
} finally {
// ๊ผญ ์ ๋ฆฌํด์ค์ผ ํจ!
accountThreadLocal.remove();
System.out.println("[" + Thread.currentThread().getName() + "] ThreadLocal ์ ๋ฆฌ ์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get());
}
});
// main์ ๋ฐ๋ก ์๋ต (remove)
System.out.println("[" + Thread.currentThread().getName() + "] ์์
์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get().getName());
accountThreadLocal.remove();
System.out.println("[" + Thread.currentThread().getName() + "] remove ์๋ฃ");
System.out.println("[" + Thread.currentThread().getName() + "] remove ์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get());
// main ๋๊ธฐ
Thread.sleep(4000);
executor.shutdown();
}
@DisplayName("์ค๋ ๋ ํ์์ ๊บผ๋ด์จ ์ค๋ ๋๋ ๊ฐ์ด ๋ณต์ฌ ์๋จ")
@Test
void testInheritableThreadLocal_pool_no_inherit() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
// MainThread์์ ์ค์
accountThreadLocal.set(new AccountInfo("main"));
System.out.println("[" + Thread.currentThread().getName() + "] set name = main");
// 2๋ฒ ์คํ -> 1๋ฒ์ ์๋ก ์์ฑ, 2๋ฒ์ ๊บผ๋ด์์ ์ฌ์ฉ
for(int i=0;i<2;i++) {
int cnt = i;
executor.submit(() -> {
try {
AccountInfo accountInfo = accountThreadLocal.get();
if (cnt == 0) {
System.out.print("์ฒซ๋ฒ์งธ๋ ์๋ก ์์ฑ๋จ => ");
} else {
System.out.print("๋๋ฒ์งธ๋ ํ์์ ๊บผ๋ด์ด => ");
}
System.out.println("[" + Thread.currentThread().getName() + "] inherited? " + (accountInfo != null ? accountInfo.getName() : "null"));
} finally {
accountThreadLocal.remove();
}
});
}
accountThreadLocal.remove();
Thread.sleep(1000);
executor.shutdown();
}
@DisplayName("์ค๋ ๋ ํ ์ฌ์ฉ ์ TaskDecorator๋ก ๋ณต์ฌ ํ ์คํ, finally์์ remove ํ์ธ")
@Test
void testInheritableThreadLocal_sub_finally_remove() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(1);
accountThreadLocal.set(new AccountInfo("main"));
System.out.println("[" + Thread.currentThread().getName() + "] ์
ํ
๋ ๊ฐ = " + accountThreadLocal.get().getName());
Runnable originalTask = () -> {
try {
System.out.println("[" + Thread.currentThread().getName() + "] ๋ณต์ฌ๋ ๊ฐ = " + accountThreadLocal.get().getName());
} finally {
// ๊ผญ remove ํด์ค์ผ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง๋จ
accountThreadLocal.remove();
System.out.println("[" + Thread.currentThread().getName() + "] remove ์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get());
}
};
// TaskDecorator์ฒ๋ผ context ์ ๋ฌ
Runnable decoratedTask = () -> {
AccountInfo parentContext = accountThreadLocal.get();
accountThreadLocal.set(parentContext);// ์์์ฒ๋ผ ๋ณต์ฌ
originalTask.run();
};
// ์คํ
executor.submit(decoratedTask).get();
// Main context๋ ์ ๋ฆฌ
accountThreadLocal.remove();
System.out.println("[" + Thread.currentThread().getName() + "] remove ์๋ฃ, ์ฝ์ ๊ฐ = " + accountThreadLocal.get());
assertThat(accountThreadLocal.get()).isNull();
executor.shutdown();
}
}
[์ฐธ๊ณ ]
- [Java] ์ค๋ ๋ ๋ก์ปฌ(ThreadLocal)๊ณผ ์์ ๊ฐ๋ฅํ ์ค๋ ๋ ๋ก์ปฌ( InheritableThreadLocal)์ ๋ํ์ฌ
- ThreadLocal์ ๋ด๋ถ ๋์ ๋ฐฉ์์ java.lang ์์ค์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋ถ์
'Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Spring] Feign Client๋? (2) | 2023.11.03 |
|---|---|
| [Spring] Mockito๋? (0) | 2023.05.21 |
- Total
- Today
- Yesterday
- ํจ์ํ ํ๋ก๊ทธ๋๋ฐ
- ์๋ฐ์คํฌ๋ฆฝํธ
- Baekjoon
- ํ๋ก๊ทธ๋๋จธ์ค
- ์ ์ญ ๋ณ์
- ์นด์นด์ค ์ธํด
- ๋์์ธ ํจํด
- ๋ฐฑ์ค javascript
- ๋คํธ์ํฌ
- ๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ
- ์ฝ๋ฉํ ์คํธ
- ์ด๋ถํ์
- map
- ๋ฐฑ์ค
- ํฌํฌ์ธํฐ
- ์๊ณ ๋ฆฌ์ฆ
- ์๋ฐ
- ํ๋กํผํฐ
- TDD
- ๋ ์์ปฌ ํ๊ฒฝ
- JavaScript
- ๋ชจ๋ ์๋ฐ์คํฌ๋ฆฝํธ deep dive
- ๋น๋๊ธฐ
- ๋ค์ด๋๋ฏน ํ๋ก๊ทธ๋๋ฐ
- 2019 ์นด์นด์ค ๊ฐ๋ฐ์ ๊ฒจ์ธ ์ธํด
- ์ด์์ฒด์
- ๋ฐฑ์ค node.js
- ํ๋กํ ์ฝ
- git
- fp
| ์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |