Concurrent programming - task execution

This chapter mainly introduces the Excutor framework.


Disadvantages of unlimited thread creation:

1. The overhead of the thread life cycle is very high.

2. Resource consumption. Active threads consume resources, especially memory.

3. Stability.

Executor framework

The problem with executing tasks serially is poor responsiveness and throughput, and the problem with assigning a thread to each task is the complexity of resource management.

Thread pools simplify thread management, and java.util.concurrent provides a flexible thread pool implementation as part of the Executor framework.

public interface Executor
{

    public abstract void execute(Runnable runnable);
}

Although Executor is a simple interface, it provides the basis for a flexible and powerful asynchronous task execution framework that supports many different types of task execution strategies. It provides a standard method to decouple the task submission process and execution process, and use Runnable to represent the task.

Create a fixed-length thread pool:

public class TaskExecutionWebServer {
	
	private static final int NTHREADS = 100;
	private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
	
	@SuppressWarnings({ "resource", "unused" })
	public static void main(String[] args) throws IOException {
		ServerSocket socket = new ServerSocket(80);
		while(true){
			final Socket connection = socket.accept();
			Runnable task = new Runnable() {
				
				@Override
				public void run() {
					// process the task
				}
			};
			exec.execute(task);
		}
	}

}

Thread Pool

A thread pool is a resource pool that manages a group of homogeneous worker threads.

Executing tasks in a thread pool has more advantages than assigning a thread to each task.

  • By reusing existing threads instead of creating new threads, the huge overhead incurred in thread creation and destruction can be amortized when processing multiple requests.
  • When the request arrives, the worker thread usually already exists, so there is no delay in the execution of the task due to waiting for the thread to be created, resulting in improved responsiveness.
  • By properly sizing the thread pool, you can create enough threads to keep the processor busy, while preventing too many threads from competing for resources and causing the application to run out of memory or fail.

To create a thread pool, use the static factory method of the Executors class:

1. newFixedThreadPool creates a fixed-length thread pool. Whenever a task is submitted, a thread is created until the maximum number of thread pools is reached, at which time the size of the thread pool does not change.

    public static ExecutorService newFixedThreadPool(int i)
    {
        return new ThreadPoolExecutor(i, i, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    }
2. newCachedThreadPool creates a cacheable thread pool. If the current size of the thread pool exceeds the processing demand, the idle threads will be recycled. When the demand increases, new threads can be added. There is no limit to the size of the thread pool.
    public static ExecutorService newCachedThreadPool()
    {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

3. newSingleThreadExecutor is a single-threaded Executor that creates a single worker thread to execute tasks.

    public static ExecutorService newSingleThreadExecutor()
    {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
    }

4. newScheduledThreadPool creates a fixed-length thread pool and executes tasks in a delayed or timed manner.

    public static ScheduledExecutorService newScheduledThreadPool(int i, ThreadFactory threadfactory)
    {
        return new ScheduledThreadPoolExecutor(i, threadfactory);
    }

Executor life cycle management :

The ExecutorService interface extends Executor and adds some methods for lifecycle management

public interface ExecutorService
    extends Executor
{

    public abstract void shutdown();

    public abstract List shutdownNow();

    public abstract boolean isShutdown();

    public abstract boolean isTerminated();

    public abstract boolean awaitTermination(long l, TimeUnit timeunit)
        throws InterruptedException;

    public abstract Future submit(Callable callable);

    public abstract Future submit(Runnable runnable, Object obj);

    public abstract Future submit(Runnable runnable);

    public abstract List invokeAll(Collection collection)
        throws InterruptedException;

    public abstract List invokeAll(Collection collection, long l, TimeUnit timeunit)
        throws InterruptedException;

    public abstract Object invokeAny(Collection collection)
        throws InterruptedException, ExecutionException;

    public abstract Object invokeAny(Collection collection, long l, TimeUnit timeunit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

The lifecycle of an ExecutorService has three states: running, shutdown, and terminated.

The ExecutorService is running when it is initially created.

The shutdown method will perform a graceful shutdown process: no new tasks will be accepted, while waiting for submitted tasks to complete, including tasks that have not yet started.

The shutdownNow method will perform a crude shutdown process: it will attempt to cancel all running tasks and will no longer start tasks in the queue that have not yet started execution.

Tasks submitted after the ExecutorService shuts down will be executed by the "rejection handler", which will either discard the task, or cause the execute method to throw an unchecked RegectedExecutionException. After all tasks are completed, the ExecutorService enters the terminated state. You can call awaitTermination to wait for the ExecutorService to reach the terminated state, or to poll whether it has terminated via isTerminated. Usually shutdown is called immediately after calling awaitTermination, resulting in the effect of synchronously shutting down the ExecutorService.

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;

public class LifecycleWebServer {
	
	private final ExecutorService exec = Executors.newCachedThreadPool();
	
	@SuppressWarnings("resource")
	public void start() throws IOException {
		ServerSocket socket = new ServerSocket(80);
		while (!exec.isShutdown()) {
			try {
				final Socket conn = socket.accept();
				exec.execute(new Runnable() {
					
					@Override
					public void run() {
						// process the task
						hanleRequest(conn);
					}
				});
			} catch (Exception e) {
				e.printStackTrace ();
			}
		}
	}
	
	public void stop(){
		exec.shutdown();
	}
	
	void hanleRequest(Socket conn){
		//If the request asks to stop the task
		stop();
		//otherwise, process the task
	}

}

Task Callable and Future that carry results

For many tasks that have deferred computation, Callable is a better abstraction: it thinks that the main entry point will return a value, and may throw an exception.

Future identifies the life cycle of a task and provides corresponding methods to determine whether it has been completed or canceled, as well as to obtain the result of the task and cancel the task.

public interface Callable
{

    public abstract Object call()
        throws Exception;
}
public interface Future
{

    public abstract boolean cancel(boolean flag);

    public abstract boolean isCancelled();

    public abstract boolean isDone();

    public abstract Object get()
        throws InterruptedException, ExecutionException;

    public abstract Object get(long l, TimeUnit timeunit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
public class FutureRenderer {
	
	private final ExecutorService executor = Executors.newCachedThreadPool();
	
	void renderPage(CharSequence source){
		final List<ImageInfo> imageInfos = scanForImageInfo(source);
		Callable<List<ImageData>> task = new Callable<List<ImageData>>() {
			@Override
			public List<ImageData> call() throws Exception {
				List<ImageData> result = new ArrayList<>();
				for(ImageInfo imageInfo: imageInfos ){
					result.add(imageInfo.downloadImage());
				}
				return result;
			}
		};
		Future<List<ImageData>> future = executor.submit(task);
		renderText(source);
		try {
			List<ImageData> imageData = future.get();
			for(ImageData data : imageData){
				renderImage(data);
			}
		} catch (Exception e) {
			future.cancel(true);
		}
		
	}
	
	
	private void renderImage(ImageData data) {
		//load image
	}


	private void renderText(CharSequence source) {
		// load text
	}


	private List<ImageInfo> scanForImageInfo(CharSequence source) {
		return null;
	}

	class ImageInfo {

		public ImageData downloadImage() {
			return null;
		}
		
	}
	
	class ImageData{
		
	}

}

If you submit a set of computing tasks to the Executor and want to get the results after the calculation is completed, you can keep the Future associated with each task, and then use the get method repeatedly, while specifying the parameter timeout as 0, so as to judge the task by polling is completed. Although this method is feasible, it is somewhat cumbersome.

The functionality of Executor and BlockingQueue can be fused together using CompletionService. Callable tasks can be submitted to it for execution, and then methods such as take and poll similar to queue operations are used to obtain completed results, which will be encapsulated into Futures when completed.

public class Renderer {
	
	private final ExecutorService executorService;

	public Renderer(ExecutorService executorService) {
		this.executorService = executorService;
	}
	
	void renderPage(CharSequence source){
		final List<ImageInfo> imageInfos = scanForImageInfo(source);
		CompletionService<ImageData> completionService = new ExecutorCompletionService<>(executorService);
		for(ImageInfo imageInfo: imageInfos ){
			completionService.submit(new Callable<Renderer.ImageData>() {
				@Override
				public ImageData call() throws Exception {
					return imageInfo.downloadImage();
				}
			});
		}
		renderText(source);
		try {
			for(int t=0 ,n =imageInfos.size();t<n ; t++){
				Future<ImageData> f =completionService.take();
				ImageData imageData = f.get();
				renderImage(imageData);
			}
		} catch (Exception e) {
			e.printStackTrace ();
		}
		
	}
	
	
	private void renderImage(ImageData data) {
		//load image
	}


	private void renderText(CharSequence source) {
		// load text
	}


	private List<ImageInfo> scanForImageInfo(CharSequence source) {
		return null;
	}

	class ImageInfo {

		public ImageData downloadImage() {
			return null;
		}
		
	}
	
	class ImageData{
		
	}

}






Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324647766&siteId=291194637