ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Java

[Spring] ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ThreadLocal ๊ด€๋ฆฌ

๊ฐœ๋ฐœ๊ฐœ๊ตด๐Ÿธ 2025. 5. 25. 19:24
728x90
๋ฐ˜์‘ํ˜•

ํ•ด๋‹น ํฌ์ŠคํŒ…์€ ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜๋Š” ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ์—์„œ ์•ˆ์ „ํ•œ 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();
    }
}

 


 

[์ฐธ๊ณ ]

 

728x90
๋ฐ˜์‘ํ˜•

'Java' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring] Feign Client๋ž€?  (2) 2023.11.03
[Spring] Mockito๋ž€?  (0) 2023.05.21