Saravanan Ramamoorthy
Saravanan Ramamoorthy

Reputation: 139

executor channel - InheritableThreadLocal issue

I am using executor channel for parallel calls.

<task:executor id="taskExecutor" pool-size="5"/>
<int:channel id="executorChannel">
    <int:dispatcher task-executor="taskExecutor"/>
</int:channel>

I am using InheritableThreadLocal variable, it is reset to the initial value after 6th call because of the pool-size declared as 5.

In other words, thread local value from 1-10 read as 1-5 and again 1-5. Other than ThreadLocal variable issue, everything is working fine.

please advise on the fix.

Here is the sample Classes: 1. HeaderContext - to store InheritableThreadLocal variable

public final class HeaderContext {

    private HeaderContext() {}

    private static final InheritableThreadLocal<String> GID = new InheritableThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return new String();
        }
    };

    public static void clear() {
        GID.remove();
    }

    public static void setGID(String gid) {
        GID.set(gid);
    }

    public static String getGID() {
        return GID.get();
    }
}

2. TestGateway

@Component
public interface TestGateway {
        String testMethod(String name);
}

3. TestActivator

@Component
public class TestActivator {

    public String testMethod(String name){
        System.out.println("GID from Inheritable thread local ->"+HeaderContext.getGID());
        return name;
    }
}

4. Test Class

@ContextConfiguration(locations = { "classpath:exec-channel-si.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class ThreadLocalGatewayTest {

    @Autowired
    TestGateway testGateway;

    @Test
    public void testBrokerageGateway() throws InterruptedException {

        for(int i=1;i<=5;i++){
            try {
                HeaderContext.setGID("gid"+i);
                System.out.println("done->" + testGateway.testMethod("name"+i));

            } finally {
                HeaderContext.clear(); 
            }
            Thread.sleep(2000);
        }
    }

}

5. SI context

<task:executor id="taskExecutor" pool-size="2"/>
<context:component-scan base-package="sample.test"/>
<int:channel id="executorChannel">
    <int:dispatcher task-executor="taskExecutor"/>
</int:channel>

<int:gateway id="testGateway"
    service-interface="sample.test.TestGateway"
    default-request-channel="executorChannel">
</int:gateway>
<int:service-activator input-channel="executorChannel" ref="testActivator"/>

Output

GID from Inheritable thread local ->gid1
output->name1
GID from Inheritable thread local ->gid2
output->name2
GID from Inheritable thread local ->gid1
output->name3
GID from Inheritable thread local ->gid2
output->name4
GID from Inheritable thread local ->gid1
output->name5

Upvotes: 1

Views: 561

Answers (1)

Artem Bilan
Artem Bilan

Reputation: 121542

Well, I'm sure when you print ThreadId in your service, you won't be surprised that you reuse thread for your purpose:

name-> name1 in the inheritable thread local -> gid1 for thread: pool-1-thread-1
name-> name2 in the inheritable thread local -> gid2 for thread: pool-1-thread-2
name-> name3 in the inheritable thread local -> gid3 for thread: pool-1-thread-3
name-> name4 in the inheritable thread local -> gid4 for thread: pool-1-thread-4
name-> name5 in the inheritable thread local -> gid5 for thread: pool-1-thread-5
name-> name6 in the inheritable thread local -> gid4 for thread: pool-1-thread-4
name-> name10 in the inheritable thread local -> gid1 for thread: pool-1-thread-1
name-> name8 in the inheritable thread local -> gid2 for thread: pool-1-thread-2
name-> name9 in the inheritable thread local -> gid3 for thread: pool-1-thread-3
name-> name7 in the inheritable thread local -> gid5 for thread: pool-1-thread-5

That is result of the simple test:

private static final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

@Test
public void testInheritableThreadLocal() throws InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();
    final CountDownLatch stopLatch = new CountDownLatch(10);
    for (int i = 1; i <= 10; i++) {
        final int j = i;
        try {
            threadLocal.set("gid" + i);
            executorService.execute(() -> {
                System.out.println("name-> " + "name " + j + " in the inheritable thread local -> "
                        + threadLocal.get() + " for thread: " + Thread.currentThread().getName());
                stopLatch.countDown();
            });
        }
        finally {
            threadLocal.remove();
        }
    }
    assertTrue(stopLatch.await(10, TimeUnit.SECONDS));
}

Now let's take a look into ThreadLocal JavaDocs:

 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable. 

So, each thread has its own copy of variables and change to them in one thread doesn't affect another thread.

The InheritableThreadLocal in the main Thread is as a normal ThreadLocal. Only with that gain that a new spawned child Thread will get a current copy of the main ThreadLocal. That's why we see a fresh values for each new thread, but when reuse that child thread, like it is in case of ThreadPool, we still see its own old value.

Please, read more RTFM on the matter!

Hope that clear

Upvotes: 1

Related Questions