Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

Creating a thread pool within a web application is usually frowned upon. Why is that?

  • If you create your thread pool within the web application context, your pool will be competing for threads with your web application.
  • Another problem with creating your thread pool within the web application context is that you don’t know what assumptions the web container makes about what is available in the thread. It could be storing information related to security, etc on the thread.
  • Another problem with creating threads within the web application context is that those threads will respond to the web application shutdown. This may be counter to what you want to happen. Those threads may need to run to completion, regardless of the desire to shutdown the web application context.

Creating our Spring thread worker

Now that we know what not to do and why, let’s start with the right way to do this. The first thing is that we need to implement a bean to perform our work. There a couple of things that will need to be different from other generic beans.

  • The beans should be scope Prototype. Spring beans default to Singleton scope. That means that there is one bean per Spring container. When you are making worker threads, they should be scoped as prototype. That means that a new bean is created whenever one is requested.
  • The class should implement the Runnable interface. This dictates that a run method must be implemented. This method is called when the thread is started as a separately executing thread.

Here is an example thread worker. It is scoped Prototype and implements Runnable. It doesn’t do much, but waits a random amount of time to simulate asynchronous work. I added a field with a custom value and a little logging so we can see the workings of the threads via the logs.

package com.javaninja.spring.webmvc.threads;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

/**
 * This class performs the work that is needed in the thread pool.
 * @author norris.shelton
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ThreadWorker implements Runnable {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private String uniqueInfo;

    private List<String> data = new LinkedList<>();


    public void setUniqueInfo(String uniqueInfo) {
        this.uniqueInfo = uniqueInfo;
    }

    public List<String> getData() {
        return data;
    }


    /**
     * When an object implementing interface {@code Runnable} is used to create a thread, starting the thread
     * causes the object's {@code run} method to be called in that separately executing thread.
     * <p>
     * The general contract of the method {@code run} is that it may take any action whatsoever.
     * @see Thread#run()
     */
    @Override
    public void run() {
        logger.info("Started runner {}", uniqueInfo);

        try {
            int milliseconds = new Random().nextInt(10000);
            logger.info("{} sleeping for {} ms", uniqueInfo, milliseconds);
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            logger.error("Trouble with thread", e);
        }

        // simulating having results
        for (int i = 0; i < 5; i++) {
            data.add(UUID.randomUUID().toString());
        }

        logger.info("Runner {} finishing data={}", uniqueInfo, data);
    }

    /**
     * Returns a string representation of the object. In general, the {@code toString} method returns a string that
     * "textually represents" this object. The result should be a concise but informative representation that is easy
     * for a person to read. It is recommended that all subclasses override this method.
     * <p>
     * The {@code toString} method for class {@code Object} returns a string consisting of the name of the class of
     * which the object is an instance, the at-sign character `{@code @}', and the unsigned hexadecimal representation
     * of the hash code of the object. In other words, this method returns a string equal to the value of: <blockquote>
     * <pre>
     * getClass().getName() + '@' + Integer.toHexString(hashCode())
     * </pre> </blockquote>
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }
}

Defining our Spring thread pool

Spring provides the ThreadPoolTaskExecutor class that takes out most of the difficulty. It provides some customization that proves to be extremely useful. Some of the settings are below:

  • corePoolSize – This is minimum pool size. The default value is 1.
  • maxPoolSize – This is the maximum pool size. the default is Integer.MAX_VALUE.
  • waitForTasksToCompleteOnShutdown – Should the pool wait for tasks to complete when the Spring container is shutdown. The default value is false.
  • awaitTerminationSeconds – How long should the Spring container wait for the pool to finish its tasks when the Spring container is told to shut down.

An example is below:

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
          p:corePoolSize="5"
          p:maxPoolSize="5"
          p:waitForTasksToCompleteOnShutdown="true"
          p:awaitTerminationSeconds="20"/>

Using a pool with fixed workers

Our first example will have a fixed number of worker threads. This is the simplest way to do it if you know you will always have a specific number of thread workers. The first thing to do to use the pool is get a reference to the TaskExecutor. Then inject the number of workers that we will need. Remember that these workers are all scoped Prototype to ensure that each can work completely independent of each other. I set a unique value into each thread worker and have each one of them sleep a random amount of time to help identify it while it is working. To start a worker we call the execute method on the thread pool with our worker as the method parameter.

package com.javaninja.spring.webmvc.threads;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * This is the code that controls the execution of a fixed number of worker threads.
 * @author norris.shelton
 */
@Service
public class FixedThreadService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    @Autowired
    private ThreadWorker worker1;

    @Autowired
    private ThreadWorker worker2;

    @Autowired
    private ThreadWorker worker3;

    @Autowired
    private ThreadWorker worker4;

    @Autowired
    private ThreadWorker worker5;

    public String withFixedThreads() {
        worker1.setUniqueInfo("1");
        taskExecutor.execute(worker1);

        worker2.setUniqueInfo("2");
        taskExecutor.execute(worker2);

        worker3.setUniqueInfo("3");
        taskExecutor.execute(worker3);

        worker4.setUniqueInfo("4");
        taskExecutor.execute(worker4);

        worker5.setUniqueInfo("5");
        taskExecutor.execute(worker5);

        return UUID.randomUUID().toString();
    }
}

Using a pool with dynamic workers

Sometimes we don’t know how many worker threads we will need until runtime. This requires a slightly different solution. We will need access to the Spring context to create as many workers as needed. Remember that the thread worker classes are scoped as Prototype. This means that each time we request a bean via getBean, that we are getting a totally new bean that will execute completely independent of any other thread worker bean. From there, the code is similar and will actually be shorter.

NOTE: to get the Spring context during the execution of the bean, you should implement ApplicationContextAware. This dictates that you have a setApplicationContext method. Once you have the context injected, you can use it however you need. To inject beans on demand, in our case.

package com.javaninja.spring.webmvc.threads;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.UUID;

/**
 * This is the code that controls the execution of a dynamic number of worker threads.
 * @author norris.shelton
 */
@Service
public class DynamicThreadService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    /**
     * Set the ApplicationContext that this object runs in. Normally this call will be used to initialize the object.
     * <p>Invoked after population of normal bean properties but before an init callback such as {@link
     * InitializingBean#afterPropertiesSet()} or a custom init-method. Invoked after {@link
     * ResourceLoaderAware#setResourceLoader}, {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and
     * {@link MessageSourceAware}, if applicable.
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws ApplicationContextException in case of context initialization errors
     * @throws BeansException              if thrown by application context methods
     * @see BeanInitializationException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public String withDynamicThreads(int threads) {

        ThreadWorker threadWorker = null;
        for (int i = 0; i < threads; i++) {
            threadWorker = applicationContext.getBean("threadWorker", ThreadWorker.class);
            threadWorker.setUniqueInfo(String.valueOf(i));
            taskExecutor.execute(threadWorker);
        }

        return UUID.randomUUID().toString();
    }
}

Running a pool with thread workers

The pool will run like normal. One thing to note is that we wanted our pool to continue processing, even if the server is requested to shut down. You can see by the logging that the threads continue to process. Success. The threads operate independent of the web application container and the Spring container.

INFO |Initializing ExecutorService  'taskExecutor' ||org.springframework.scheduling.concurrent.ExecutorConfigurationSupport:165 
INFO |Started runner 4 ||com.javaninja.spring.webmvc.threads.ThreadWorker:48 
INFO |Started runner 1 ||com.javaninja.spring.webmvc.threads.ThreadWorker:48 
INFO |Started runner 2 ||com.javaninja.spring.webmvc.threads.ThreadWorker:48 
INFO |Started runner 5 ||com.javaninja.spring.webmvc.threads.ThreadWorker:48 
INFO |Started runner 3 ||com.javaninja.spring.webmvc.threads.ThreadWorker:48 
INFO |4 sleeping for 8116 ms ||com.javaninja.spring.webmvc.threads.ThreadWorker:52 
INFO |1 sleeping for 586 ms ||com.javaninja.spring.webmvc.threads.ThreadWorker:52 
INFO |2 sleeping for 6754 ms ||com.javaninja.spring.webmvc.threads.ThreadWorker:52 
INFO |5 sleeping for 8060 ms ||com.javaninja.spring.webmvc.threads.ThreadWorker:52 
INFO |3 sleeping for 8841 ms ||com.javaninja.spring.webmvc.threads.ThreadWorker:52 
INFO |Closing org.springframework.context.support.GenericApplicationContext@491cc5c9: startup date [Tue May 10 10:01:27 EDT 2016]; root of context hierarchy ||org.springframework.context.support.AbstractApplicationContext:960 
INFO |Shutting down ExecutorService 'taskExecutor' ||org.springframework.scheduling.concurrent.ExecutorConfigurationSupport:203 
INFO |Runner 1 finishing data=[8aa9a123-5d0f-42d0-8be4-b703705a7a5c, 10de5358-5c7f-4881-9f9d-7706ea6ae8dd, 3f23f9c8-a594-4228-8a91-5eab26fb4d39, 6a52ec9c-82ec-4b88-bfdd-4b413291a8fe, d000aa8c-8c4d-4f6f-9dd5-c63c44431bcd] ||com.javaninja.spring.webmvc.threads.ThreadWorker:63 
INFO |Runner 2 finishing data=[cceed519-1cc9-4ce0-bdf4-02703f624853, 342f024c-c785-47a7-b4b4-aec261341fa7, 56c54700-37ac-41aa-89a2-3d1b4f92b6ed, 2ba81d54-d52b-4b4c-a26a-06996d25c2b9, c7a5e66c-b196-4936-a5aa-5ada8b416b7d] ||com.javaninja.spring.webmvc.threads.ThreadWorker:63 
INFO |Runner 5 finishing data=[159e842e-aaf8-4f86-9e01-1c814043943d, 59fa91bf-cd6a-4ad2-b7bd-8de85d5223f2, 360da0f9-91c8-4682-8d88-23a4f7327b98, a23824c5-f52a-426b-8c16-f5ec223c90cd, 0862d9a1-1635-4d15-8f7f-911877ac7165] ||com.javaninja.spring.webmvc.threads.ThreadWorker:63 
INFO |Runner 4 finishing data=[e26c4269-804c-4a6c-ae08-dc798e6f550c, 709bbf44-f480-4f32-834b-d5c8fc095b81, 807d311d-5a68-48b0-b2a6-cb74d596ee32, e4beeeeb-6628-4261-8a44-dc19a205c71a, a87c24b8-f693-4461-a54d-8a101fbc2a59] ||com.javaninja.spring.webmvc.threads.ThreadWorker:63 
INFO |Runner 3 finishing data=[389b2d85-4a64-4ea8-8b35-39480e027614, 5c85f6d0-a59e-4b7a-95be-e0ab9822e487, efb164c3-dc07-4a73-ae1f-c14c2f05ba08, 33a78ef4-8d67-4566-b20b-c74044012086, e75222f3-bf78-486f-b9e3-9b244f3237e3] ||com.javaninja.spring.webmvc.threads.ThreadWorker:63 

Process finished with exit code 0

Project files

The code for this project is in sheltonn / spring-web-threads

May 10th, 2016

Posted In: Java, java ninja, Javaninja, Spring

Leave a Reply

Your email address will not be published. Required fields are marked *

LinkedIn Auto Publish Powered By : XYZScripts.com