[Java Concurrency 01] A First Look at Java Thread Concurrency

Hits: 0

Article directory

Knowledge reserve

We know that the creation of threads in Java is created using Thread. When writing simple test code or business code, we may also directly use new Thread to create thread tasks.

But can this approach really satisfy business demands? Or is there still room for further tuning of these written codes?

These issues may need to be considered on resource-constrained or highly responsive architectures.

This article is mainly based on the thread in Java and its concurrency library for a first look.

OS API

First of all, let’s see what API support is provided for the operation of our thread by the operating system that our program runs on. Based on this, let’s look at the implementation of Java. Here I take the standard library Pthreads in the standard POSIX (Portable Operating System Interface, Portable Operating System Interface) as an example.

A set of thread operation function interfaces are defined in Pthreads (this time only the operation part is analyzed):

pthread_create() // Create a thread 
pthread_exit() // Terminate the current thread 
pthread_cancel() // Request to interrupt the running of another thread. Threads that are requested to be interrupted continue to run until a certain Cancellation Point is reached. A cancellation point is a place where a thread checks to see if it has been canceled and acts as requested. There are two types of POSIX cancellation (Cancellation Type), one is delayed cancellation (PTHREAD_CANCEL_DEFERRED), which is the default cancellation type of the system, that is, there will be no real cancellation until the thread reaches the cancellation point; the other is asynchronous cancellation (PHREAD_CANCEL_ASYNCHRONOUS), when using asynchronous cancellation, the thread can cancel at any time. The cancellation point of the system call is actually the time period from when the cancellation type in the function is changed to asynchronous cancellation until it is changed back to delayed cancellation. Almost all library functions that can suspend a thread will respond to the CANCEL signal and terminate the thread, including delay functions such as sleep and delay. 
pthread_join() // Block the current thread until another thread finishes running 
pthread_kill() // Send a signal to the thread with the specified ID. If the thread does not handle the signal, it will act on the entire process according to the default behavior of the signal. The signal value of 0 is a reserved signal, which is used to determine whether the thread is still alive according to the return value of the function. 
pthread_cleanup_push()// A thread can arrange functions to be called when it exits abnormally. Such a function is called a thread cleaner, and a thread can create multiple cleaners. The entry address of the thread cleaning program is stored in the stack, and the principle of advanced post-processing is implemented. When the thread ends caused by pthread_cancel or pthread_exit, the functions pushed by pthread_cleanup_push will be executed sequentially. The return statement of the thread function executing the return statement does not cause the thread cleanup procedure to be executed. 
pthread_cleanup_pop() // When called with a non-zero argument, causes the currently popped thread cleaner to execute. 
pthread_setcancelstate() // Enable or disable the cancellation of another thread. 
pthread_setcanceltype() // Set the thread's cancellation type to delayed cancellation or asynchronous cancellation.

All operation functions are referenced from the POSIX thread – function section, and the full interface can refer pthread.hto all the interfaces in this section.

API provided by java.lang.Thread

Does the above interface look familiar? We have found java.lang.Threadmany similar externally provided interfaces in Java’s standard thread library:

Thread currentThread () ; // Get the reference of the current thread 
void  yield () ; // Thread yield (temporarily give up CPU resources, let the thread return to RUNNABLE state, do not accept interruption) 
void  sleep ( long millis)  throws InterruptedException ; // Thread sleep 
void  start () ; // The actual thread creation function 
void  join ( final  long millis)  throws InterruptedException ; // Block the main thread and wait for the completion of the sub-thread task execution

In fact, it is not difficult to find here that the definition and functions of the interface provided by Thread in Java are actually very similar to the Posix thread interface to a large extent. Later, we will also dig deeper into the relationship between the two from the source code level, and this time we will not go into details.

Thread Pool

From the previous project experience and the immersion of the knowledge we have accumulated in various predecessors, we know that under multi-threaded programming, it is usually more efficient to use thread pools than we simply use threads for business management , management is more convenient. Here I pay tribute to Professor Doug Lea, who can design and hand out the core code in the thread pool and maintain it all the time.

In Java, the main task execution body in the process of the heart of Thread is Runnable. The Doug Leaold man specially defined a Runnable submission interface ExecutorService, and ExecutorServicethe main interfaces provided to the outside world are:

shutdown(); // Try to shut down ExecutorService, but will not execute 
shutdownNow() immediately; // Try to stop the thread immediately by initiating a terminal, but in fact, there is no guarantee that all threads will be able to stop 
Future <? > submit(Runnable task ); // Submit Runnable tasks and wait for ExecutorService to execute 
<T> List <Future<T>> invokeAll(Collection <? extends Callable<T>> tasks) throws InterruptedException; // Attempt to execute all submitted threads 
<T> T invokeAny (Collection <? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; // As long as one thread succeeds, stop all other threads

The specific implementation principle of the above interface, and how to use it reasonably, I will analyze it step by step in the future.

ExecutorServiceIt has its default implementation abstract class AbstractExecutorService, and AbstractExecutorServiceas a basic abstract class, it extends several current mainstream thread pool schemes:

  1. ThreadPoolExecutor: The most classic thread pool execution entity class
  2. ScheduledThreadPoolExecutor: In fact, it is an extension class of ThreadPoolExecutor, which executes threads regularly as a supplementary capability;
  3. ForkJoinPool: The Fork-Join framework is a Doug Leanew feature introduced by the old man in Java 1.7, and the above two thread pool technologies are APIs that have been introduced in Java 1.5.

other supplementary knowledge

  1. Operating system knowledge, this requires diligent reading
  2. Source code knowledge, this needs to continue to read the code

test code

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public  class  TimeTest {
     public  static  void  main ( String[] args ) throws InterruptedException {
         // Create a thread-specific variable 
        ThreadLocal<Object> obj = new ThreadLocal<>();
        Runnable runnable = () -> {
            if (obj.get() == null) obj.set(new Object());
        };
        System.out.println("multiple thread cost: " + new TestThread().testMultiTaskByThread(runnable));
        System.out.println("thread pool cost: " + new TestThreadPool().testMultiTaskByThreadPool(runnable));
        System.out.println("ForkJoinTask cost: " + new TestForkJoinTask().testMultiTaskByForkJoinPool(runnable));
        System.out.println("Stream.parallel cost: " + new TestStreamParallel().testStreamParallel(runnable));
    }
}

class TestThread {
    long testMultiTaskByThread(Runnable runnable) throws InterruptedException {
        long current = System.currentTimeMillis();
        for (int i = 0; i < 100_000; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
            thread.join();
        }
        return System.currentTimeMillis() - current;
    }
}

class TestThreadPool {
    long testMultiTaskByThreadPool(Runnable runnable) {
        ExecutorService executor = Executors.newFixedThreadPool(8);
        long current = System.currentTimeMillis();
        for (int i = 0; i < 100_000; i++) {
            executor.execute(runnable);
        }
        executor.shutdownNow();
        return System.currentTimeMillis() - current;
    }
}

class TestForkJoinTask {
    long testMultiTaskByForkJoinPool(Runnable runnable) {
        Executor executor = Executors.newWorkStealingPool();
        long current = System.currentTimeMillis();
        for (int i = 0; i < 100_000; i++) {
            executor.execute(runnable);
        }
        return System.currentTimeMillis() - current;
    }
}

class TestStreamParallel {
    long testStreamParallel(Runnable runnable) {
        long current = System.currentTimeMillis();
        List<Integer> countList = new ArrayList<>();
        for (int i = 0; i < 100_000; i++) {
            countList.add(i);
        }
        countList.parallelStream().forEach(i -> runnable.run());
        return System.currentTimeMillis() - current;
    }
}

Test Results

multiple thread cost: 9691
thread pool cost: 26
ForkJoinTask cost: 32
Stream.parallel cost: 8

You may also like...

Leave a Reply

Your email address will not be published.