A detailed explanation of concurrent programming in one article

Detailed explanation of concurrent programming

I have recently studied: Binghe's "In-depth Understanding of High Concurrency Programming" ; "The Art of Concurrent Programming" ;
hereby briefly summarizes the study to facilitate the subsequent improvement and consolidation of concurrent programming knowledge;
if you want to understand the study in depth, you can read the above reference original;

Threads and thread pools

process

Process is the basic unit of resource allocation in the system. When a modern operating system runs a program, it creates a process for it.

The process is the space allocated by the application in the memory, which is the running program. Each process does not interfere with each other.

An operating system that uses process + CPU time slice rotation can handle multiple tasks at the same time at a macro level. In other words, processes make the concurrency of the operating system possible. Although concurrency looks at a macro level, there are multiple tasks executing, but in fact, for a single-core CPU, only one task is occupying CPU resources at any specific moment.

thread

Threads are the basic unit of CPU scheduling. It is a unit that is smaller than a process and can be run independently.

In a process, multiple threads can be created. These threads can have their own private counters, stack memory and local variables, and can access shared memory variables.

Multithreading

In the same program, multiple threads can be run at the same time to perform different tasks; these threads can use multiple cores of the CPU to run at the same time.

Why use multithreading? The most important thing is: multi-threaded programming can maximize the use of multi-core CPU resources.

context switch

Context switching refers to the CPU switching from one process (or thread) to another process (or thread).

Context refers to the contents of the CPU registers and program counter at a certain moment

Context switching is usually computationally intensive, meaning that this operation consumes a lot of CPU time, so more threads are not always better . How to reduce the number of context switches in the system is an important topic to improve multi-threading performance.

How threads are implemented

  1. Inherit the Thread class

    public class ThreadDemo {
          
          
    
        public static class TestThread extends Thread {
          
          
            @Override
            public void run() {
          
          
                System.out.println("extends Thread");
            }
        }
    
        public static void main(String[] args) {
          
          
            TestThread testThread = new TestThread();
            testThread.start();
        }
    
        //console:extends Thread
    }
    
  2. Implement the Runnable interface

    public class ThreadDemo {
          
          
    
        public static class TestThreadRun implements Runnable {
          
          
            @Override
            public void run() {
          
          
                System.out.println("extends Thread2");
            }
        }
    
        public static void main(String[] args) {
          
          
            Thread thread = new Thread(new TestThreadRun());
            thread.start();
        }
    
        //console:extends Thread2
    }
    
  3. Implement the Callable interface

    public class ThreadDemo {
          
          
    
        public static class TestThreadCall implements Callable<String> {
          
          
            @Override
            public String call() {
          
          
                System.out.println("extends Thread2");
                return "result:do success";
            }
        }
    
        public static void main(String[] args) {
          
          
            TestThreadCall call = new TestThreadCall();
            String result = call.call();
            System.out.println(result);
        }
    
        //console:
        // extends Thread2
        //result:do success
    }
    

thread priority

In Java, the thread priority is controlled through an integer member variable priority, which ranges from 1 to 10. The larger the value, the higher the priority. The default is 5.

When threads with high priority are allocated CPU time slices, the allocation probability is higher than that of threads with low priority.

Note: Thread priority cannot be relied upon for program correctness (high priority is not guaranteed to execute earlier than low priority)

Thread execution order

In the same method, after multiple threads are created continuously, the order in which the thread's start() method is called does not determine the execution order of the threads.

How to ensure the execution order of threads?

  1. You can use the join() method in the Thread class to ensure the execution order of threads. join() actually makes the main thread wait for the current child thread to complete execution .
  2. The join() method will call the local wait() method internally. When the thread wait() method is called, the main thread will be in a waiting state, waiting for the child thread to complete execution before executing

Thread life cycle

Several important states in the thread life cycle:

  1. NEW: The thread is created, but the start() method has not been called;

  2. RUNNABLE: Runnable state, including ready state and running state;

  3. BLOCKED: Blocked state. Threads in this state need to wait for other threads to release the lock or wait to enter synchronized;

  4. WAITTING: Waiting state. Threads in this state need to wait for other threads to wake up or interrupt operations before entering the next state;

    Calling the following three methods will put the thread into a waiting state:

    1. Object.wait(): puts the current thread in a waiting state until another thread wakes it up
    2. Thread.join(): Wait for the thread to complete execution. The underlying call is the wait() method of Object.
    3. LockSupport.park(): Disables the current thread for thread scheduling unless permission is granted to call it.
  5. TIME_WAITTING: Timeout waiting state, which can return by itself after the specified time;

  6. TERMINATED: Termination status, indicating that the thread has completed execution;

Thread life cycle flow chart (abbreviated):

Insert image description here

Note: Use jps combined with the jstack command to threadly analyze Java thread exception information in the production environment.

In-depth understanding of Thread class

Thread class definition

public class Thread implements Runnable {
    
    ...}

@FunctionalInterface
public interface Runnable {
    
    
    public abstract void run();
}

The Thread class implements the Runnable interface, and the Rnnnable interface has only one run() method and is modified by the @FunctionalInterface annotation

Load local resources

/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
    
    
    registerNatives();
}

This method is mainly used to load some local resources and call this local method in a static code block

Thread member variable

 //线程名称
 private volatile String name;
 //优先级
 private int            priority;
 private Thread         threadQ;
 private long           eetop;

 /* Whether or not to single_step this thread. */
 //是否单步线程
 private boolean     single_step;

 /* Whether or not the thread is a daemon thread. */
 //是否守护线程
 private boolean     daemon = false;

 /* JVM state */
 private boolean     stillborn = false;

 /* What will be run. */
 //实际执行体
 private Runnable target;

 /* The group of this thread */
 private ThreadGroup group;

 /* The context ClassLoader for this thread */
 private ClassLoader contextClassLoader;

 /* The inherited AccessControlContext of this thread */
 //访问控制上下文
 private AccessControlContext inheritedAccessControlContext;

 /* For autonumbering anonymous threads. */
 //为匿名线程生成名称的编号
 private static int threadInitNumber;
 private static synchronized int nextThreadNum() {
    
    
     return threadInitNumber++;
 }

 /* ThreadLocal values pertaining to this thread. This map is maintained
  * by the ThreadLocal class. */
 //与此线程相关的ThreadLocal
 ThreadLocal.ThreadLocalMap threadLocals = null;

 /*
  * InheritableThreadLocal values pertaining to this thread. This map is
  * maintained by the InheritableThreadLocal class.
  */
 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

 //当前线程请求的堆栈大小
 private long stackSize;
 //线程终止后存在的JVM私有状态
 private long nativeParkEventPointer;
 //线程ID
 private long tid;

 /* For generating thread ID */
 //用于生成线程ID
 private static long threadSeqNumber;

 /* Java thread status for tools,
  * initialized to indicate thread 'not yet started'
  */
 //线程状态,初始为0,表示未启动
 private volatile int threadStatus = 0;

 /**
  * The argument supplied to the current call to
  * java.util.concurrent.locks.LockSupport.park.
  * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
  * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
  */
 volatile Object parkBlocker;

 /* The object in which this thread is blocked in an interruptible I/O
  * operation, if any.  The blocker's interrupt method should be invoked
  * after setting this thread's interrupt status.
  */
 //Interruptible中定义了中断方法,用来中断特定线程
 private volatile Interruptible blocker;
 //当前线程的内部锁
 private final Object blockerLock = new Object();
 //线程最小优先级
 public final static int MIN_PRIORITY = 1;
 //默认优先级
 public final static int NORM_PRIORITY = 5;
 //最大优先级
 public final static int MAX_PRIORITY = 10;

It can be seen from the member variables that the Thread class is not a task, but a real thread object. The target variable of the Runnable type inside it is the actual task.

Thread state definition

public enum State {
    
    
    /**
     * Thread state for a thread which has not yet started.
     */
    //新建状态;线程被创建,但是还没有调用start()方法
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    //可运行状态;包括运行中状态和就绪状态
    RUNNABLE,

    //阻塞状态;此状态的线程需要等待其他线程释放锁,或者等待进入synchronized
    BLOCKED,

    //等待状态;此状态的线程需要其他线程对其进行唤醒或者中断状态,进而进入下一状态
    WAITING,

    //超时等待状态;可以在一定的时间自行返回
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    //终止状态;当前线程执行完毕
    TERMINATED;
}

Constructor method of Thread class

public Thread() {
    
    
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    
    
        init(null, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(ThreadGroup group, Runnable target) {
    
    
        init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    
    
        init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
    
    
        init(group, null, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    
    
        init(group, target, name, 0);
}

Through several commonly used construction methods of the Thread class, we found that the initialization of the Thread class is mainly achieved through the init() method.

init() method

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    
    
    if (name == null) {
    
    
        //名称为空
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    //安全管理器
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
    
    
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
    
    
            //获取线程组
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
    
    
            //线程组为空,从父类获取
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
    
    
        if (isCCLOverridden(getClass())) {
    
    
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();
    //当前线程继承父线程相关属性
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

The construction method of the Thread class is called by the main thread that creates the Thread thread. At this time, the main thread that calls the construction method is the parent thread of Thread; in the init() method, the newly created Thread thread will inherit part of the parent thread. Attributes

run() method

@Override
public void run() {
    
    
    if (target != null) {
    
    
        target.run();
    }
}

It can be seen that the implementation of the run() method of the Thread method is relatively simple. It is actually implemented by running the run() method on a Runnable type target;

It should be noted that directly calling the run method of the Runnable interface will not create a new thread to perform the task; if you need to create a new thread to perform the task, you need to call the start() method of the Thread class;

start() method

public synchronized void start() {
    
    
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    //标记线程是否启动
    boolean started = false;
    try {
    
    
        //调用本地方法启动
        start0();
        //变更线程启动标识
        started = true;
    } finally {
    
    
        try {
    
    
            if (!started) {
    
    
                //未启动,则线程组中标记为启动失败
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
    
    
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

It can be seen from the source code that the start() method is modified by synchronized, indicating that this method is synchronous. It will check the status of the thread before the thread is actually started. If it is not 0 (NEW status), it will directly return an exception; so a A thread can only be started once. If it is started multiple times, an exception will be reported ;

After calling the start() method, the newly created thread will be in the ready state (if it is not scheduled by the CPU). When the CPU is idle, it will be scheduled by the CPU for execution. At this time, the thread is in the running state, and the JVM will call the thread's run( ) method to perform tasks

sleep() method

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException {
    
    
    if (millis < 0) {
    
    
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
    
    
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
    
    
        millis++;
    }

    sleep(millis);
}

The sleep() method will cause the thread to sleep for a period of time. It should be noted that the lock will not be released after calling the sleep() method to put the thread to sleep.

join() method

The usage scenario of the join() method is usually the thread that starts the thread to execute the task, calls the join() method of the execution thread, and waits for the execution thread to execute the task until it times out or the execution thread terminates.

interrupt() method

Thread interruption:

In some cases, after we start the thread, we find that we do not need to run it and need to interrupt this thread. Currently there is no safe and direct way to stop a thread in JAVA, but JAVA introduces a thread interruption mechanism to handle situations where threads need to be interrupted.

The thread interruption mechanism is a cooperative mechanism. It should be noted that the interrupt operation does not directly terminate the thread running, but notifies the interrupted thread to handle it on its own.

Several methods are provided in the Thread class to handle thread interruptions:

  1. Thread.interrupt(): Interrupt the thread. The interrupt thread here does not immediately terminate the thread running, but sets the thread interrupt flag to true.
  2. Thread.currentThread.isInterrupt(): Tests whether the current thread is interrupted. The interrupt status of the thread is affected by this method, which means that calling it once will set the thread's interruption status to true, and calling it twice will reset the thread's interruption status to false.
  3. Thread.isInterrupt(): Tests whether the current thread is interrupted. Unlike the above method, this method does not change the thread interruption status.

This method is used to interrupt the execution of the current thread. It interrupts the current thread by setting the thread's interrupt flag bit. At this time, if the interrupt flag bit is set for the thread,

An InterruptException may be thrown and the interrupt status of the current thread will be cleared. This way of interrupting the thread is safer. It allows the task being executed to continue to be completed, unlike stop which forces the thread to close.

public void interrupt() {
    
    
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
    
    
        Interruptible b = blocker;
        if (b != null) {
    
    
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

Expired suspend()/resume()/stop()

The three methods suspend()/resume()/stop() can be simply understood as suspending, resuming and terminating threads. Since the methods are outdated, they are not recommended for use.

Callable and Future

Callable interface

The Callable interface can obtain the return result after thread execution; while inheriting Thread and implementing the Runnable interface cannot obtain the execution result.

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


@FunctionalInterface
public interface Callable<V> {
    
    
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

It can be seen that the Callable interface is similar to the Runnable interface. It is also a functional interface with only one method; the difference is that the method provided by Callable has a return value and supports generics.

Callable is generally used in conjunction with the thread sub-tool ExecutorService class. We will delve into the use of thread pools in subsequent chapters.

Here we only introduce that ExecutorService can call the submit method to execute a Callable and return a Future. Subsequent programs can obtain the execution result through the Future's get method.

public class TestTask implements Callable<String> {
    
    
    @Override
    public String call() throws Exception {
    
    
        //模拟程序执行需要一秒
        Thread.sleep(1000);
        return "do success!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        ExecutorService executorService = Executors.newCachedThreadPool();
        TestTask testTask = new TestTask();
        Future<String> submitRes = executorService.submit(testTask);

        //注意调用get方法会阻塞当前线程,知道得到结果
        //实际编码中建议使用设有超时时间的重载get方法
        String reslut = submitRes.get();
        System.out.println(reslut);
    }
    
    //console:
    // do success!
}

Asynchronous model

  1. Asynchronous model without return result

    Asynchronous tasks that do not return results can be directly thrown into threads or thread pools to run. At this time, the task execution results cannot be obtained directly. One way is to obtain the running results through the callback method; the implementation method is similar to the observer mode.

  2. Asynchronous models that return results

    The JDK provides a solution that can directly obtain and return asynchronous results:

    1. Use Future to get results

      The Future interface is often used in conjunction with the thread pool to obtain asynchronous results.

    2. Get results using FutureTask

      The FutureTask class can be used in conjunction with the Thread class or with the thread pool.

Future interface

  1. Future interface

    public interface Future<V> {
          
          
        
        boolean cancel(boolean mayInterruptIfRunning);
    
        boolean isCancelled();
    
        boolean isDone();
       
        V get() throws InterruptedException, ExecutionException;
        
        V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeotException;
    
    1. boolean cancel(boolean)
      1. Cancel the execution of the task, receiving a boolean type parameter, and returns true if the cancellation is successful, otherwise it returns false.
      2. The task has been completed, ended or cannot be canceled. Return false, indicating that the cancellation failed.
      3. If the task has not been started, calling this method returns true, indicating successful cancellation.
      4. If the task has been started, it will be determined based on the input boolean parameter whether to cancel the task by interrupting the thread.
    2. boolean isCancelled()
      1. Judgment task is canceled before completion
      2. If the task is canceled before completion, return true, otherwise return false
      3. Note: True will be returned only if the task is not started and canceled before completion, otherwise it will return false.
    3. boolean isDone()
      1. Determine whether the task has been completed
      2. If the task ends normally, exits with an exception, or is canceled, it will return true, indicating that the task is completed.
    4. V get()
      1. When the task is completed, the result data of the task is returned directly.
      2. When not completed, wait for the task to complete and return the result
    5. V get(long,TimeUnit)
      1. When the task is completed, the task completion result is returned directly.
      2. If it is not completed, it will wait for the return result within the timeout period. If the time is exceeded, a TimeOutException exception will be thrown.
  2. RunnableFuture interface

    public interface RunnableFuture<V> extends Runnable, Future<V> {
          
          
        /**
         * Sets this Future to the result of its computation
         * unless it has been cancelled.
         */
        void run();
    }
    

    The RunnabeleFuture interface not only inherits the Future interface, but also inherits the Runnable interface, so it has the abstract interfaces of both.

  3. FutureTask class

    public class FutureTask<V> implements RunnableFuture<V> {
          
          
        //详细源码,后续分析
    }
    

    The FutureTask class is a very important implementation class of the RunnableFuture interface. It implements all abstract methods of the RunnableFuture interface, Future interface and Runnnable interface.

    1. Variables and constants in the FutureTask class

      First, a volatile-modified variable state is defined. Volatile variables achieve thread safety through memory barriers and prohibiting reordering;

      Then define several status constants when the task is running;

      (In the code comments, several possible status changes are given)

      	/* Possible state transitions:
           * NEW -> COMPLETING -> NORMAL
           * NEW -> COMPLETING -> EXCEPTIONAL
           * NEW -> CANCELLED
           * NEW -> INTERRUPTING -> INTERRUPTED
           */
          private volatile int state;
          private static final int NEW          = 0;
          private static final int COMPLETING   = 1;
          private static final int NORMAL       = 2;
          private static final int EXCEPTIONAL  = 3;
          private static final int CANCELLED    = 4;
          private static final int INTERRUPTING = 5;
          private static final int INTERRUPTED  = 6;
      

      Next, several member variables are defined;

      	/** The underlying callable; nulled out after running */
          private Callable<V> callable;
          /** The result to return or exception to throw from get() */
          private Object outcome; // non-volatile, protected by state reads/writes
          /** The thread running the callable; CASed during run() */
          private volatile Thread runner;
          /** Treiber stack of waiting threads */
          private volatile WaitNode waiters;
      
      1. callable: perform specific tasks by calling the run() method;
      2. outcaom: the return result or exception information obtained through the get() method
      3. runner: the thread used to run the callable interface and ensure thread safety through CAS
      4. waiters: the stack of waiting threads. In derived classes, CAS and this stack will be used to switch the running state.
    2. Construction method

      Two different construction methods for passing parameters;

      
          public FutureTask(Callable<V> callable) {
              
              
              if (callable == null)
                  throw new NullPointerException();
              this.callable = callable;
              this.state = NEW;       // ensure visibility of callable
          }
      
          /**
           * Creates a {@code FutureTask} that will, upon running, execute the
           * given {@code Runnable}, and arrange that {@code get} will return the
           * given result on successful completion.
           *
           * @param runnable the runnable task
           * @param result the result to return on successful completion. If
           * you don't need a particular result, consider using
           * constructions of the form:
           * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
           * @throws NullPointerException if the runnable is null
           */
          public FutureTask(Runnable runnable, V result) {
              
              
              this.callable = Executors.callable(runnable, result);
              this.state = NEW;       // ensure visibility of callable
          }
      
    3. isCancelled() and isDone()

          public boolean isCancelled() {
              
              
              return state >= CANCELLED;
          }
      
          public boolean isDone() {
              
              
              return state != NEW;
          }
      

      In both methods, whether it has been canceled or completed is determined by judging the size of the status value;

      What can be learned here is that when defining status values ​​in the future, try to follow certain change rules. In this way, for scenarios where status changes frequently, regular status values ​​may have the effect of getting twice the result with half the effort when performing business logic.

    4. cancel(boolean) method

      public boolean cancel(boolean mayInterruptIfRunning) {
              
              
              if (!(state == NEW &&
                    UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                        mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                  return false;
              try {
              
                  // in case call to interrupt throws exception
                  if (mayInterruptIfRunning) {
              
              
                      try {
              
              
                          Thread t = runner;
                          if (t != null)
                              t.interrupt();
                      } finally {
              
               // final state
                          UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                      }
                  }
              } finally {
              
              
                  finishCompletion();
              }
              return true;
          }
      

      First, it will be quickly judged whether it can be canceled based on the status judgment or CAS operation result; if the status is not NEW or CAS returns false, cancellation failure will be returned directly;

      Then in the try code block, first determine whether it can be interrupted to cancel; if so, define a reference pointing to the running task, and determine whether the task is empty. If not, call the interrupt method, and then modify the running status to cancel;

      Finally, call the finishCompletion() method in the finally code block to end the task running;

      /**
           * Removes and signals all waiting threads, invokes done(), and
           * nulls out callable.
           */
          private void finishCompletion() {
              
              
              // assert state > COMPLETING;
              for (WaitNode q; (q = waiters) != null;) {
              
              
                  if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
              
              
                      for (;;) {
              
              
                          Thread t = q.thread;
                          if (t != null) {
              
              
                              q.thread = null;
                              LockSupport.unpark(t);
                          }
                          WaitNode next = q.next;
                          if (next == null)
                              break;
                          q.next = null; // unlink to help gc
                          q = next;
                      }
                      break;
                  }
              }
              done();
              callable = null;        // to reduce footprint
          }
      

      In the finishCompletion() method, a for loop is first defined, looping waiters (thread waiting stack), and the loop termination condition is that waiters is empty; inside the specific loop, first determine whether the CAS operation is successful, and if successful, define a new spin Loop; in the spin loop, the thread in the stack will be awakened to complete its operation. After completion, break will jump out of the loop, and finally the done() method will be called and the callable will be empty;

    5. get() method

      
          public V get() throws InterruptedException, ExecutionException {
              
              
              int s = state;
              if (s <= COMPLETING)
                  s = awaitDone(false, 0L);
              return report(s);
          }
      
          public V get(long timeout, TimeUnit unit)
              throws InterruptedException, ExecutionException, TimeoutException {
              
              
              if (unit == null)
                  throw new NullPointerException();
              int s = state;
              if (s <= COMPLETING &&
                  (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
                  throw new TimeoutException();
              return report(s);
          }
      

      The get() method without parameters will block and wait for the task to be completed when the task is not completed; the get() method with parameters will block and wait for completion, but after the given time period exceeds, a TimeoutException will be thrown;

      /**
           * Awaits completion or aborts on interrupt or timeout.
           *
           * @param timed true if use timed waits
           * @param nanos time to wait, if timed
           * @return state upon completion
           */
          private int awaitDone(boolean timed, long nanos)
              throws InterruptedException {
              
              
              final long deadline = timed ? System.nanoTime() + nanos : 0L;
              WaitNode q = null;
              boolean queued = false;
              for (;;) {
              
              
                  if (Thread.interrupted()) {
              
              
                      removeWaiter(q);
                      throw new InterruptedException();
                  }
      
                  int s = state;
                  if (s > COMPLETING) {
              
              
                      if (q != null)
                          q.thread = null;
                      return s;
                  }
                  else if (s == COMPLETING) // cannot time out yet
                      Thread.yield();
                  else if (q == null)
                      q = new WaitNode();
                  else if (!queued)
                      queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                           q.next = waiters, q);
                  else if (timed) {
              
              
                      nanos = deadline - System.nanoTime();
                      if (nanos <= 0L) {
              
              
                          removeWaiter(q);
                          return state;
                      }
                      LockSupport.parkNanos(this, nanos);
                  }
                  else
                      LockSupport.park(this);
              }
          }
      

      The awaitDone() method mainly waits for the completion or interruption of execution;

      The most important one is the for spin loop. In the loop, it will first determine whether it is interrupted. If it is interrupted, removeWaiter() is called to remove the waiting stack and throw an interrupt exception; if it is not interrupted, the logic is executed to determine whether it is completed;

    6. set() and setException()

      
          protected void set(V v) {
              
              
              if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
              
              
                  outcome = v;
                  UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
                  finishCompletion();
              }
          }
      
          protected void setException(Throwable t) {
              
              
              if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
              
              
                  outcome = t;
                  UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
                  finishCompletion();
              }
          }
      

      The logic of the two methods is almost the same, except that one is set to NORMAL when setting the task status, and the other is set to EXCEPTIONAL;

    7. run() and runAndReset()

      public void run() {
              
              
              if (state != NEW ||
                  !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                               null, Thread.currentThread()))
                  return;
              try {
              
              
                  Callable<V> c = callable;
                  if (c != null && state == NEW) {
              
              
                      V result;
                      boolean ran;
                      try {
              
              
                          result = c.call();
                          ran = true;
                      } catch (Throwable ex) {
              
              
                          result = null;
                          ran = false;
                          setException(ex);
                      }
                      if (ran)
                          set(result);
                  }
              } finally {
              
              
                  // runner must be non-null until state is settled to
                  // prevent concurrent calls to run()
                  runner = null;
                  // state must be re-read after nulling runner to prevent
                  // leaked interrupts
                  int s = state;
                  if (s >= INTERRUPTING)
                      handlePossibleCancellationInterrupt(s);
              }
          }
      

      It can be said that if Future or FutureTask() is used, the run() method will inevitably be called to run the task;

      In the run() method, it will first determine whether it is in the NEW state or the CAS operation returns false, and will return directly without continuing to execute;

      In the next try code block, the call() method of the callable is executed and the result is received;

    8. removeWaiter() method

      
          private void removeWaiter(WaitNode node) {
              
              
              if (node != null) {
              
              
                  node.thread = null;
                  retry:
                  for (;;) {
              
                        // restart on removeWaiter race
                      for (WaitNode pred = null, q = waiters, s; q != null; q = s){
              
              
                          s = q.next;
                          if (q.thread != null)
                              pred = q;
                          else if (pred != null) {
              
              
                              pred.next = s;
                              if (pred.thread == null) // check for race
                                  continue retry;
                          }
                          else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                                q, s))
                              continue retry;
                      }
                      break;
                  }
              }
          }
      

      This method mainly removes the thread in the WaitNode (waiting stack) through a spin loop;

In-depth analysis of ThreadPoolExecutor

Java's thread sub-technology is one of the core technologies of Java. In the field of Java high concurrency, it is a topic that can never be avoided.

Disadvantages of Thread directly creating threads

  1. A new thread is created every time new Thread() is used, without reuse and poor performance;
  2. Threads lack unified management, and new threads may be created without restrictions, which may occupy a large amount of resources and cause OOM or crash;
  3. Lack of more control operations, such as more executions, regular executions, interruptions, etc.;

Benefits of using thread pool

  1. Existing threads can be reused, reducing the overhead caused by new threads and improving performance;
  2. It can effectively control the maximum number of concurrencies, improve resource utilization, and reduce resource competition caused by too many threads;
  3. Provides functions such as scheduled execution, periodic execution, single thread, and concurrency control;
  4. Provide methods to support thread pool monitoring, which can monitor the running status of the thread pool in real time;

Thread Pool

  1. Executors
    1. newCachedThreadPool: Create a bufferable thread pool. If the size of the thread pool exceeds processing needs, idle threads can be flexibly recycled. If there are no threads to recycle, a new thread will be created;
    2. newFixedThreadPool: Create a fixed-length thread pool, which can control the maximum number of concurrent threads. Threads exceeding the fixed length will wait in the queue;
    3. newScheduledThreadPool: Create a fixed-length thread pool that can be executed regularly and periodically;
    4. newSingleThreadPool: Create a single-threaded thread pool and use a unique thread to execute thread tasks, ensuring that all tasks are executed in the specified order;
    5. newSingleScheduleThreadPool: Create a single-threaded thread pool that can be executed regularly and periodically;
    6. newWorkStealingThreadPool: Create a work-stealing thread pool with parallel level;

Several states of thread pool instances

  1. Running

    Running state, can accept newly submitted tasks, and can also process tasks in the blocking queue

  2. Shutdown

    In the closed state, it no longer accepts newly submitted tasks, but can process tasks saved in the blocking queue;

    Running --> shutdown() --> Shutdown

  3. Stop

    In the stopped state, new tasks cannot be received, and tasks in the blocking queue cannot be processed, which will interrupt tasks being processed;

    Running/Shutdown --> shutdownNow() --> Stop

  4. Tidying

    Clear state, all tasks have been terminated, the number of effective threads is 0 (the number of threads in the blocking queue is 0, and the number of threads executing in the thread pool is also 0)

  5. Terminated

    End status, Tidying --> terminate() --> Terminated

Note: There is no need to do special processing for the thread pool status. The thread pool status is defined and processed by the thread pool internally according to the method.

Suggestions for properly configuring the number of threads

  1. For CPU-intensive tasks that need to squeeze the CPU, the number of threads can be set to ncpu+1 (number of CPUs + 1)
  2. For IO-intensive tasks, the number can be set to ncpu*2 (2 times the number of CPUs)

ThreadPoolExecutor, the core class of thread pool

  1. Construction method

    /**
         * Creates a new {@code ThreadPoolExecutor} with the given initial
         * parameters and default thread factory and rejected execution handler.
         * It may be more convenient to use one of the {@link Executors} factory
         * methods instead of this general purpose constructor.
         *
         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @throws IllegalArgumentException if one of the following holds:<br>
         *         {@code corePoolSize < 0}<br>
         *         {@code keepAliveTime < 0}<br>
         *         {@code maximumPoolSize <= 0}<br>
         *         {@code maximumPoolSize < corePoolSize}
         * @throws NullPointerException if {@code workQueue} is null
         */
        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
          
          
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
        }
    

    This is the constructor with the most parameters, and other constructors are overloaded with this method.

    1. corePoolSize: number of core threads
    2. maximumPoolSize: maximum number of threads
    3. workQueue: blocking queue, storing tasks waiting to be executed

    The relationship between the above three parameters is as follows:

    • If the number of running threads is less than corePoolSize, a new thread will be created directly to run; even if other threads in the thread pool are idle
    • If the number of running threads is greater than or equal to corePoolSize and less than maximumPoolSize, the excess number will enter the workQueue and wait. Only when the workQueue is full will a new thread be created.
    • If corePoolSize is equal to maximumPoolSize, then the thread pool size is fixed at this time. If a new task is submitted and the workQueue is not full, it will enter the workQueue and wait for an idle thread to be taken out from the workQueue for execution.
    • If the number of running threads exceeds maximumPoolSize and the workQueue is full, the policy will be processed through the reject policy rejectHandler

    Based on the above parameters, the thread will process the task as follows:

    When a new task is submitted to the thread pool, the thread pool will perform different processing methods based on the number of currently running threads; there are three main processing methods: direct switching, using unbounded queues, and using bounded queues.

    • Directly switching the commonly used queue is SynchronousQueue
    • Using an infinite queue means using a queue based on a linked list. For example, LinkedBlockingQueue, when using this queue, the maximum number of threads created in the thread pool is corePoolSize, maximumPoolSize will not work
    • Using a bounded queue means using an array-based queue. For example, ArrayBlockingQueue, using this method, can limit the maximum number of threads in the thread pool to maximumPoolSize, which can reduce resource consumption; but this method makes it more difficult to schedule threads, because the number of thread pools and the number of queues are both Fixed

    Based on the above parameters, we can simply draw some measures to reduce resource consumption:

    • If you want to reduce system resource consumption, context switching overhead, etc., you can set a larger queue capacity and a smaller thread pool capacity, which will reduce the throughput of thread processing tasks.
    • If submitted tasks are often blocked, you can reset the maximum number of threads to a larger number.
    1. keepAliveTime: When the thread is not executing tasks, the maximum length of time to wait for termination

      When the number of threads in the thread pool exceeds corePoolSize, if no new tasks are submitted, the threads that exceed the number of core threads will not be destroyed immediately, but will be destroyed after waiting for keepAliveTime.

    2. unit: time unit of keepAliveTime

    3. threadFactory: thread factory, used to create threads

      By default, a thread factory will be created by default. The threads created by the default factory have the same priority and are non-daemon. The thread name is also set.

    4. rejectHandler: rejection strategy

      If the workQueue is full and there are no idle threads, the rejection policy will be executed.

      The thread pool provides a total of four rejection strategies:

      • Throw an exception directly, which is also the default strategy. The implementation class is AbortPolicy
      • Use the caller's thread to execute, and the implementation class is CallerRunsPolicy
      • Discard the frontmost task in the queue and execute the current task. The implementation class is DiscardOldestPolicy
      • Directly discard the current task, the implementation class is DiscardPolicy
  2. How to start and stop

    1. execute(): submit the task to the thread pool for execution
    2. submit(): Submit a task and can return results, equivalent to execute+Future
    3. shutDown(): close the thread pool and wait for the thread to complete execution
    4. shutDownNow(): close the thread pool immediately without waiting for the thread to complete execution
  3. Methods suitable for monitoring

    1. getTaskCount(): Get the total number of executed and unexecuted threads
    2. getCompletedTaskCount(): Get the number of threads that have completed execution
    3. getCorePoolSize(): Get the number of core threads
    4. getActiveCount(): or get the number of running threads

In-depth analysis of top-level interfaces and abstract classes in thread pools

Overview of interfaces and abstract classes

Insert image description here

  • Executor interface: The top-level interface of the thread pool, which provides a method to submit tasks without a return value.
  • ExecutorService: Inherited from Executor, it extends many functions, such as closing the thread pool, submitting tasks and returning results, waking up tasks in the thread pool, etc.
  • AbstractExecutotService: Inherits from ExecutorService and implements some very practical methods for subclasses to call
  • ScheduledExecutorService: Inherits from ExecutorService and extends methods related to scheduled tasks

Executor interface

public interface Executor {
    
    
    void execute(Runnable command);
}

The Executor interface is relatively simple and provides a method execute(Runnable command) to execute submitted tasks.

ExecutorService interface

The ExecutorService interface is the core interface of the non-scheduled task thread pool. Through this interface, tasks are submitted to the thread pool (supporting both return and non-return methods), closing the thread pool, waking up thread tasks, etc.

public interface ExecutorService extends Executor {
    
    
    //关闭线程池,不再接受新提交的任务,但之前提交的任务继续执行,直到完成
    void shutdown();

    //立即关闭线程池,不再接受新提交任务,会尝试停止线程池中正在执行的任务
    List<Runnable> shutdownNow();

    //判断线程池是否已经关闭
    boolean isShutdown();

    //判断线程中所有任务是否已经结束,只有调用shutdown()或shutdownNow()之后,调用此方法才返回true
    boolean isTerminated();

    //等待线程中所有任务执行结束,并设置超时时间
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
        
    //提交一个Callable类型的任务,并返回一个Future类型的结果
    <T> Future<T> submit(Callable<T> task);

    //提交一个Runnable类型任务,并设置泛型接收结果数据,返回一个Futrue类型结果
    <T> Future<T> submit(Runnable task, T result);

    //提交一个Runnable类型任务,并返回一个Future类型结果
    Future<?> submit(Runnable task);

    //批量提交Callable类型任务,并返回他们的执行结果,Task列表和Future列表一一对应
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
        
    //批量提交Callable类型任务,并获取返回结果,并限定处理所有任务的时间
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    //批量提交任务,并获得一个已经成功执行任务的结果
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    //批量提交任务,并获得一个已经完成任务的结果,并限定处理任务时间
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

AbstractExecutorEservice

​ This class is an abstract class that inherits from ExecutorEservice, and on this basis, implements some practical methods for subclasses to call.

1. newTaskFor()


    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    
    
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    
    
        return new FutureTask<T>(callable);
    }

FutureTask is used to obtain running results. In practical applications, we often use its subclass FutureTask;

The function of the newTaskFor method is to encapsulate the task into a FutureTask object, and then submit the FutureTask object to the thread pool.

2. doInvokeAny()

This method is a core method that executes thread pool tasks in batches and ultimately returns a result data; as long as this method obtains the result of one of the threads, it will cancel other running threads in the thread pool;

3. invokeAny()

Inside this method, the doInvokeAny() method is still called to submit a batch of threads. One of them completes and returns the result, and the remaining threads cancel the operation;

4. invokeAll()

The invokeAll() method implements logic with and without timeout settings;

The method logic without timeout setting is: encapsulate the submitted batch tasks into RunnableFuture objects, then call the execute() method to execute the task, and add the resulting Future to the Future collection. Afterwards, the Future collection will be traversed to determine whether the task is The execution is completed; if it is not completed, the get method will be called to block until the result is obtained, and the exception will be ignored at this time; finally, in finally, the completion identification of all tasks is judged, and if it is not completed, the execution is canceled;

5. submit()

This method is relatively simple. It encapsulates the task into a RunnableFuture object. After calling execute(), the Future result is returned.

ScheduleExecutorService

​ This interface inherits from ExecutorService. In addition to inheriting the methods of the parent class, it also provides the function of scheduled task processing.

Source code perspective analysis of how to create a thread pool

​ Executors.newWorkStealingPool: This method is a new method of creating a thread pool in Java 8. It can set the parallel level for threads and has higher concurrency and performance; in addition, other methods of creating thread pools, They are all called constructors of ThreadPoolExecutor;

ThreadPoolExecutor creates a thread pool

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
    
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

By looking at the source code of the ThreadPoolExecutor class, we found that the thread is ultimately constructed by calling the exception construction method. Other initialization parameters have been introduced previously;

ForkJoinPool class creates thread pool

public static ExecutorService newWorkStealingPool(int parallelism) {
    
    
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

    public static ExecutorService newWorkStealingPool() {
    
    
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

As can be seen from the above source code, the Executors.newWorkStealingPool() method, when constructing the thread pool, actually calls ForkJoinPool to construct the thread pool;

/**
     * Creates a {@code ForkJoinPool} with the given parameters, without
     * any security checks or parameter validation.  Invoked directly by
     * makeCommonPool.
     */
    private ForkJoinPool(int parallelism,
                         ForkJoinWorkerThreadFactory factory,
                         UncaughtExceptionHandler handler,
                         int mode,
                         String workerNamePrefix) {
    
    
        this.workerNamePrefix = workerNamePrefix;
        this.factory = factory;
        this.ueh = handler;
        this.config = (parallelism & SMASK) | mode;
        long np = (long)(-parallelism); // offset ctl counts
        this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
    }

Looking at the source code, we learned that the various construction methods of ForkJoinPool ultimately call the above-mentioned private construction method;

The initialization parameters are as follows:

  • parallelism: concurrency level
  • factory: factory for creating threads
  • handler: When the thread in the thread throws an uncaught exception, handle it through this UncaughtExceptionHandler
  • mode: value indicates FIFO_QUEUE or LIFO_QUEUE
  • workerNamePrefix: thread name prefix for executing tasks

ScheduledThreadPoolExecutor creates a thread pool

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
    
    
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

ScheduledThreadPoolExecutor inherits from ThreadPoolExecutor. Looking at its source code, we can see that the essence of its construction method is to call the construction method of ThreadPoolExecutor, but the queue is passed DelayedWorkQueue.

Source code analysis: How to run ThreadPoolExecutor correctly

Important properties in ThreadPoolExecutor

  • ctl related attributes

    The constant ctl of type AomaticInteger is used throughout the entire life cycle of the thread pool.

    	//主要用来保存线程状态和线程数量,前3位保存线程状态,低29位报存线程数量
    	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    	//线程池中线程数量(32-3)
        private static final int COUNT_BITS = Integer.SIZE - 3;
    	//线程池中的最大线程数量
        private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
        // runState is stored in the high-order bits
    	//线程池的运行状态
        private static final int RUNNING    = -1 << COUNT_BITS;
        private static final int SHUTDOWN   =  0 << COUNT_BITS;
        private static final int STOP       =  1 << COUNT_BITS;
        private static final int TIDYING    =  2 << COUNT_BITS;
        private static final int TERMINATED =  3 << COUNT_BITS;
    
        // Packing and unpacking ctl
    	//获取线程状态
        private static int runStateOf(int c)     {
          
           return c & ~CAPACITY; }
    	//获取线程数量
        private static int workerCountOf(int c)  {
          
           return c & CAPACITY; }
        private static int ctlOf(int rs, int wc) {
          
           return rs | wc; }
    
        /*
         * Bit field accessors that don't require unpacking ctl.
         * These depend on the bit layout and on workerCount being never negative.
         */
    
        private static boolean runStateLessThan(int c, int s) {
          
          
            return c < s;
        }
    
        private static boolean runStateAtLeast(int c, int s) {
          
          
            return c >= s;
        }
    
        private static boolean isRunning(int c) {
          
          
            return c < SHUTDOWN;
        }
    
        /**
         * Attempts to CAS-increment the workerCount field of ctl.
         */
        private boolean compareAndIncrementWorkerCount(int expect) {
          
          
            return ctl.compareAndSet(expect, expect + 1);
        }
    
        /**
         * Attempts to CAS-decrement the workerCount field of ctl.
         */
        private boolean compareAndDecrementWorkerCount(int expect) {
          
          
            return ctl.compareAndSet(expect, expect - 1);
        }
    
        /**
         * Decrements the workerCount field of ctl. This is called only on
         * abrupt termination of a thread (see processWorkerExit). Other
         * decrements are performed within getTask.
         */
        private void decrementWorkerCount() {
          
          
            do {
          
          } while (! compareAndDecrementWorkerCount(ctl.get()));
        }
    
  • Other important attributes

    	//用于存放任务的阻塞队列
    	private final BlockingQueue<Runnable> workQueue;
    
    	//可重入锁
        private final ReentrantLock mainLock = new ReentrantLock();
    
        /**
         * Set containing all worker threads in pool. Accessed only when
         * holding mainLock.
         */
    	//存放线程池中线程的集合,访问这个集合时,必须先获得mainLock锁
        private final HashSet<Worker> workers = new HashSet<Worker>();
    
        /**
         * Wait condition to support awaitTermination
         */
    	//在锁内部阻塞等待条件完成
        private final Condition termination = mainLock.newCondition();
    
    	//线程工厂,以此来创建新线程
        private volatile ThreadFactory threadFactory;
    
        /**
         * Handler called when saturated or shutdown in execute.
         */
    	//拒绝策略
        private volatile RejectedExecutionHandler handler;
    	/**
         * The default rejected execution handler
         */
    	//默认的拒绝策略
        private static final RejectedExecutionHandler defaultHandler =
            new AbortPolicy();
    

Important internal classes in ThreadPoolExecutor

  • Worker

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable
        {
          
          
            /**
             * This class will never be serialized, but we provide a
             * serialVersionUID to suppress a javac warning.
             */
            private static final long serialVersionUID = 6138294804551838833L;
    
            /** Thread this worker is running in.  Null if factory fails. */
            final Thread thread;
            /** Initial task to run.  Possibly null. */
            Runnable firstTask;
            /** Per-thread task counter */
            volatile long completedTasks;
    
            /**
             * Creates with given first task and thread from ThreadFactory.
             * @param firstTask the first task (null if none)
             */
            Worker(Runnable firstTask) {
          
          
                setState(-1); // inhibit interrupts until runWorker
                this.firstTask = firstTask;
                this.thread = getThreadFactory().newThread(this);
            }
    
            /** Delegates main run loop to outer runWorker  */
            public void run() {
          
          
                runWorker(this);
            }
    
            // Lock methods
            //
            // The value 0 represents the unlocked state.
            // The value 1 represents the locked state.
    
            protected boolean isHeldExclusively() {
          
          
                return getState() != 0;
            }
    
            protected boolean tryAcquire(int unused) {
          
          
                if (compareAndSetState(0, 1)) {
          
          
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
                return false;
            }
    
            protected boolean tryRelease(int unused) {
          
          
                setExclusiveOwnerThread(null);
                setState(0);
                return true;
            }
    
            public void lock()        {
          
           acquire(1); }
            public boolean tryLock()  {
          
           return tryAcquire(1); }
            public void unlock()      {
          
           release(1); }
            public boolean isLocked() {
          
           return isHeldExclusively(); }
    
            void interruptIfStarted() {
          
          
                Thread t;
                if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
          
          
                    try {
          
          
                        t.interrupt();
                    } catch (SecurityException ignore) {
          
          
                    }
                }
            }
        }
    

    The Work class implements the Runnable interface and needs to rewrite the run() method. The essence of the Worker's run method is to call the runWorker() method of ThreadPoolExecutor.

  • Deny policy

    In the thread pool, if the WorkQueue queue is full and there are no idle threads, when a new task is submitted, the rejection policy will be executed;

    The thread pool provides a total of four rejection strategies:

    • Throwing an exception directly is also the default policy; the implementation class is AbortPolicy
    • Use the caller thread to perform tasks; the implementation class is CallerRunsPolicy
    • Discard the frontmost task in the queue and execute the current task; DiscardOldestPolicy
    • Directly discard the current task; the implementation class is DiscardPolicy

    In ThreadPoolExecutor, four internal classes are provided to implement corresponding strategies;

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code CallerRunsPolicy}.
             */
            public CallerRunsPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                if (!e.isShutdown()) {
          
          
                    r.run();
                }
            }
        }
    
        /**
         * A handler for rejected tasks that throws a
         * {@code RejectedExecutionException}.
         */
        public static class AbortPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates an {@code AbortPolicy}.
             */
            public AbortPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                throw new RejectedExecutionException("Task " + r.toString() +
                                                     " rejected from " +
                                                     e.toString());
            }
        }
    
        /**
         * A handler for rejected tasks that silently discards the
         * rejected task.
         */
        public static class DiscardPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code DiscardPolicy}.
             */
            public DiscardPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
            }
        }
    
        /**
         * A handler for rejected tasks that discards the oldest unhandled
         * request and then retries {@code execute}, unless the executor
         * is shut down, in which case the task is discarded.
         */
        public static class DiscardOldestPolicy implements RejectedExecutionHandler {
          
          
            /**
             * Creates a {@code DiscardOldestPolicy} for the given executor.
             */
            public DiscardOldestPolicy() {
          
           }
    
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          
          
                if (!e.isShutdown()) {
          
          
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
        }
    

    We can also customize the rejection policy by implementing the RejectedExecutionHandler interface and overriding the rejectedExecution() method;

    When creating a thread, pass in our custom rejection policy through the constructor of ThreadPoolExecutor;

Source code analysis ThreadPoolExecutor core process

There is a worker thread collection in ThreadPoolExecutor. Users can add tasks to the thread pool. Threads in the workers collection can directly execute tasks, or obtain tasks from the task queue and execute them;

ThreadPoolExecutor provides the entire thread pool process from creation, task execution, to death;

In ThreadPoolExecutor, the logic of the thread pool is mainly reflected in execute(Runnable command), addWorker(Runnable firstTask, boolean core), addWorkerFailed(Worker w) and other methods and rejection strategies; next, we will analyze these core methods in depth.

execute(Runnable command)

The function of this method is to submit Runnable type tasks to the thread pool;

public void execute(Runnable command) {
    
    
        if (command == null)
            //若提交的任务为空,则提交空指针异常
            throw new NullPointerException();
       //获取线程池的状态,和线程池中线程数量
        int c = ctl.get();
    	//若线程池中线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
    
    
            //重新开启线程执行任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	//若线程池处于RUNNING状态,则将任务添加到阻塞队列中
    	//只有线程池处于RUNNING状态时,才能添加到队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            //再次获取线程次状态和线程池数量,用于二次检查
            //向队列中添加线程成功,但由于其他线程可能会修改线程池状态,所以这里需要进行二次检查
            int recheck = ctl.get();
            //如果线程池没有再处于RUNNING状态,则从队列中删除任务
            if (! isRunning(recheck) && remove(command))
                //执行拒绝策略
                reject(command);
            else if (workerCountOf(recheck) == 0)
                //若线程池为空,则新建一个线程加入
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            //任务队列已满,则新建一个Worker线程,若新增失败,则执行拒绝策略
            reject(command);
    }

addWorker(Runnable firstTask, boolean core)

This method can be generally divided into three parts. The first part is mainly about CAS safely adding worker threads to the thread pool; the second part is about adding new worker threads; the third part is about adding threads to workers through safe concurrency. , and start the thread to perform the task

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    //循环标签,重试的标识
    retry:
    for (;;) {
    
    
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //检查队列是否在某些特定条件下为空
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
		//此循环中主要是通过CAS的方式增加线程个数
        for (;;) {
    
    
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //CAS的方式增加线程数量
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    //跳出最外层循环,说明已通过CAS增加线程成功
    //此时创建新的线程
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        //将新建线程封装成Woker
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            //独占锁,保证操作workers的同步
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //将Worker加入队列
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
    
    
                //释放独占锁
                mainLock.unlock();
            }
            if (workerAdded) {
    
    
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorkerFailed(Worker w)

In the addWorker(Runnable firstTask, boolean core) method, if adding the worker thread fails, or the worker thread fails to start, the addWorkerFailed(Worker w) method is called; this method is simpler, obtain the exclusive lock, and remove it from the workers. tasks, and reduce the number of tasks by one through CAS, and finally release the lock;

private void addWorkerFailed(Worker w) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
    
    
        mainLock.unlock();
    }
}
  • Deny policy

    /**
     * Invokes the rejected execution handler for the given command.
     * Package-protected for use by ScheduledThreadPoolExecutor.
     */
    final void reject(Runnable command) {
          
          
        handler.rejectedExecution(command, this);
    }
    

    The four implementation classes of RejectExecutionHandler are exactly the implementation classes of the four rejection strategies provided by the thread;

    The specific strategy of this method is determined based on the parameters passed in when creating the thread pool; by default, the default rejection strategy is used;

Source code analysis of Woker execution process in thread pool

Woker class analysis

From the class structure point of view, Woker inherits the AQS (AbstractQueueSynchronizer) class and implements the Runnable interface; essentially the Woker class is a synchronization component and a thread that performs tasks;

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    
    
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
    
    
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
    
    
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
    
    
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
    
    
        if (compareAndSetState(0, 1)) {
    
    
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
    
    
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        {
    
     acquire(1); }
    public boolean tryLock()  {
    
     return tryAcquire(1); }
    public void unlock()      {
    
     release(1); }
    public boolean isLocked() {
    
     return isHeldExclusively(); }

    void interruptIfStarted() {
    
    
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
            try {
    
    
                t.interrupt();
            } catch (SecurityException ignore) {
    
    
            }
        }
    }
}

As can be seen in the constructor of the Worker class, the synchronization state is first set to -1. This is to prevent the runWorker method from being interrupted before running;

This is because if other threads call the shutdownNow() method in the thread pool, if the state in the Worker class is > 0, the thread will be interrupted, and if the state is -1, the thread will not be interrupted;

The Worker class implements the Runnable interface and needs to override the run method, which actually calls the runWorker() method of ThreadPoolExecutor;

runWorker(Worker w)

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //释放锁,将state设置为0,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
       //若任务不为空,或队列中获取的任务不为空,则进入循环
        while (task != null || (task = getTask()) != null) {
    
    
            //任务不为空,则先获取woker线程的独占锁
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //若线程次已经停止,线程中断时未中断成功
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                //执行中断操作
                wt.interrupt();
            try {
    
    
                //任务执行前置逻辑
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
    
    
                    //任务执行
                    task.run();
                } catch (RuntimeException x) {
    
    
                    thrown = x; throw x;
                } catch (Error x) {
    
    
                    thrown = x; throw x;
                } catch (Throwable x) {
    
    
                    thrown = x; throw new Error(x);
                } finally {
    
    
                    //任务执行后置逻辑
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                //任务执行完成后,将其置空
                task = null;
                //已完成任务数加一
                w.completedTasks++;
                //释放锁
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
    
    
        //执行Worker完成退出逻辑
        processWorkerExit(w, completedAbruptly);
    }
}

From the above source code analysis, we can know that when the task obtained by Woker from the thread is empty, the getTask() method will be called to obtain the task from the queue;

getTask()

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    //自旋
    for (;;) {
    
    
        int c = ctl.get();
        //获取线程池状态
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //检测队列在线程池关闭或停止时,是否为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            //减少Worker线程数量
            decrementWorkerCount();
            return null;
        }
        //获取线程池中线程数量
        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
    
    
            //从任务队列中获取任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                //任务不为空,直接返回
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}
  • beforeExecute(Thread t, Runnable r)

    protected void beforeExecute(Thread t, Runnable r) {
          
           }
    

    The body of this method is empty, which means that you can create a subclass of ThreadPoolExecutor to override this method, so that our customized pre-logic can be executed before the thread pool actually executes the task;

  • afterExecute(Runnable r, Throwable t)

    protected void afterExecute(Runnable r, Throwable t) {
          
           }
    

    Same as above, we can override this method in a subclass, so that our custom post-processing logic can be executed after the thread pool executes the task.

  • processWorkerExit(Worker w, boolean completedAbruptly)

    The main logic of this method is to execute the logic of exiting the Worker thread and perform some cleanup work;

  • tryTerminate()

    final void tryTerminate() {
          
          
        //自旋
        for (;;) {
          
          
            //获取ctl
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) {
          
           // Eligible to terminate
                //若当前线程池中线程数量不为0,则中断线程
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
            //获取线程池的全局锁
            final ReentrantLock mainLock = this.mainLock;
            //加锁
            mainLock.lock();
            try {
          
          
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
          
          
                    try {
          
          
                        terminated();
                    } finally {
          
          
                        //将线程状态设置为TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒所有因调用awaitTermination()而阻塞的线程
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
          
          
                //释放锁
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
    
  • terminate()

    protected void terminated() {
          
           }
    

    The body of this method is empty. We can override this method in its subclass, so that in the tryTerminated() method, our customized method can be executed;

How does the source code parsing thread pool achieve graceful exit?

shutdown()

When using a thread pool, when the shutdown() method is called, the thread pool will no longer accept newly submitted tasks, and already running threads will continue to execute;

This method is a non-blocking method. It will return immediately after being called and will not wait for all thread tasks to be completed before returning;

public void shutdown() {
    
    
    //获取线程池的全局锁
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        //检查是否有关闭线程池的权限
        checkShutdownAccess();
        //将当前线程池的状态设置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        //中断woker线程
        interruptIdleWorkers();
        //调用ScheduledThreadPoolExecutor的钩子函数
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        //释放锁
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownNow()

If the shutdownNow() method of the thread pool is called, the thread will no longer receive newly submitted tasks, the threads in the workQueue queue will also be discarded, and the running threads will be interrupted; the method will return immediately, and the return result is the task List of discarded tasks in the queue workQueue

public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        //清空、丢弃队列中任务
        tasks = drainQueue();
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    //返回任务列表
    return tasks;
}

awaitTermination(long timeout, TimeUnit unit)

When the thread pool calls awaitTermination, it will block the caller's thread and will not return until the thread pool status changes to TERNINATED, or the timeout period is reached.

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    
    
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (;;) {
    
    
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
    
    
        //释放锁
        mainLock.unlock();
    }
}

The overall logic of this method is: first obtain the exclusive lock of the Worker thread, then spin, and determine that the thread pool status changes to TERMINATED; if so, return true, otherwise check whether it times out, if it times out, return false; if it does not time out, restart Set the remaining timeout period;

Key classes in AQS

CountDownLatch

  • Overview

Synchronization helper class, which can block the execution of the current thread. That is, one or more threads can wait until other threads finish executing. Use a given counter for initialization. The operation of the counter is an atomic operation, that is, only one thread can operate the counter at the same time.

The thread that calls the await() method of the modified class will wait until other threads call the countDown() method of the class and make the current counter value 0;

Each time the countDown() method of this class is called, the counter value will be decremented by one;

When the value of the counter decreases to 0, all threads blocked and waiting by calling the await() method will continue to execute; this operation can only occur once, because the value of the counter cannot be reset;

If you need a version that resets the count, consider using CyclicBarrier.

CountDownLatch supports waiting for a given timeout period, and does not wait for more than the given time; when using it, you only need to pass in the given time in the await() method;

public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
           BrokenBarrierException,
           TimeoutException {
    
    
    return dowait(true, unit.toNanos(timeout));
}
  • scenes to be used

    In some scenarios, the program needs to wait for one or more conditions to be completed before continuing to perform subsequent operations. A typical application is parallel computing: when processing a task with a large amount of calculations, it can be split into multiple small tasks. After waiting for all sub-tasks to be completed, the parent task then obtains the results of all sub-tasks and summarizes them.

  • code example

    Calling the shutdown() method of ExecutorService will not destroy all threads immediately, but will allow all existing threads to be executed.

    Then destroy the thread pool

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CountDownLatchExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        test(threadNum);
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    } finally {
          
          
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await();
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            Thread.sleep(100);
            log.info("{}", threadNum);
        }
    }
    

    The code that supports waiting for a given time is as follows:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class CountDownLatchExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        test(threadNum);
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    } finally {
          
          
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await(10, TimeUnit.MICROSECONDS);
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            Thread.sleep(100);
            log.info("{}", threadNum);
        }
    }
    

Semaphore

  • Overview

    Control the number of concurrent threads at the same time. It can control the semaphore and control the number of threads that access a certain resource at the same time.

    Provides two core methods: acquire() and release()

    acquire() means to obtain an access permission, and if it is not obtained, it will block and wait; release() will release a permission after completion.

    Semaphore maintains the currently accessible number; it uses a synchronization mechanism to control the number that can be accessed simultaneously.

    Semaphore can implement limited size linked lists

  • scenes to be used

    Semaphore is often used for resources with limited access

  • code example

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire(); //获取一个许可
                        test(threadNum);
                        semaphore.release(); //释放一个许可
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

    Acquire and release multiple licenses at a time

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire(3); //获取多个许可
                        test(threadNum);
                        semaphore.release(3); //释放多个许可
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

    Suppose there is such a scenario. Assume that the maximum number of concurrencies currently allowed by the system is 3. If it exceeds 3, it needs to be discarded. Such a scenario can also be realized through Semaphore:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class SemaphoreExample {
          
          
        private static final int threadCount = 200;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService exec = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < threadCount; i++) {
          
          
                final int threadNum = i;
                exec.execute(() -> {
          
          
                    try {
          
          
                        //尝试获取一个许可,也可以尝试获取多个许可,
                        //支持尝试获取许可超时设置,超时后不再等待后续线程的执行
                        //具体可以参见Semaphore的源码
                        if (semaphore.tryAcquire()) {
          
          
                            test(threadNum);
                            semaphore.release(); //释放一个许可
                        }
                    } catch (InterruptedException e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            log.info("finish");
            exec.shutdown();
        }
    
        private static void test(int threadNum) throws InterruptedException {
          
          
            log.info("{}", threadNum);
            Thread.sleep(1000);
        }
    }
    

CyclicBarrier

  • Overview

    It is a synchronization auxiliary class that allows a group of threads to wait for each other until they reach a common barrier point; through it, multiple threads can wait for each other. Only when each thread is ready, can each thread continue to execute. .

    Similar to countDownLatch, they are all implemented using counters. When a thread calls the await() method of CyclicBarrier, it enters the waiting state, and the counter performs an increment operation; when the counter value increases to the set initial value, All threads that enter the waiting state due to the await() method will be awakened and continue to perform their subsequent operations. CyclicBarrier can be reused after releasing the waiting thread, so CyclicBarrier is also called a cycle barrier.

  • scenes to be used

    It can be used in scenarios where multi-threads calculate data and finally merge the calculation results.

  • The difference between countDownLatch and countDownLatch

    1. The counter of countDownLatch can only be used once; while the counter of CyclicBarrier can be reset using reSet() and used cyclically.
    2. What countDownLatch implements is that 1 or n threads wait for other threads to complete before they can continue to execute. It describes the relationship between 1 or more threads waiting for other threads; and CyclicBarrier mainly means that threads in a thread group wait for each other. Each thread meets the common conditions before continuing to execute, describing the internal waiting relationship of multiple threads.
    3. CyclicBarrier can handle more complex scenarios. When a calculation error occurs, the counter can be reset and the thread can be executed again.
    4. CyclicBarrier provides more useful methods, such as getNumberByWaiting() to obtain the number of waiting threads, and the isBroken() method to determine whether a thread is interrupted.
  • code example

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            cyclicBarrier.await();
            log.info("{} continue", threadNum);
        }
    }
    

    The sample code for setting the wait timeout is as follows:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            try {
          
          
                cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
            } catch (BrokenBarrierException | TimeoutException e) {
          
          
                log.warn("BarrierException", e);
            }
            log.info("{} continue", threadNum);
        }
    }
    

    When declaring CyclicBarrier, you can also specify a Runnable. When the thread reaches the barrier, the Runnable method can be executed first. The sample code is as follows:

    package io.binghe.concurrency.example.aqs;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @Slf4j
    public class CyclicBarrierExample {
          
          
        private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
          
          
            log.info("callback is running");
        });
    
        public static void main(String[] args) throws Exception {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
          
          
                final int threadNum = i;
                Thread.sleep(1000);
                executorService.execute(() -> {
          
          
                    try {
          
          
                        race(threadNum);
                    } catch (Exception e) {
          
          
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    
        private static void race(int threadNum) throws Exception {
          
          
            Thread.sleep(1000);
            log.info("{} is ready", threadNum);
            cyclicBarrier.await();
            log.info("{} continue", threadNum);
        }
    }
    

Key lock in AQS

ReentrantLock

  • Overview

    The locks provided in Java are mainly divided into two categories, one is the Synchronized modified lock, the other is the lock provided in JUC, and the core lock in JUC is ReentrantLock

    The difference between ReentrantLock and Synchronized:

    1. Reentrancy

      When the same thread enters both of them once, the lock counter will be incremented by one. When the lock counter drops to 0, the lock will be released.

    2. Lock implementation

      Synchronized is implemented based on JVM; ReentrantLock is implemented based on JDK

    3. Performance difference

      Before Synchronized optimization, the performance was much worse than ReentrantLock; but after JDK6, after Synchronized introduced biased locks and lightweight locks (ie, spin locks), the performance was almost the same.

    4. Functional difference

      Convenience:

      Synchronized is more convenient to use, and the compiler locks and releases the lock; ReentrantLock requires manual locking and releasing, and it is best to release the lock in finally

      Flexibility and granularity:

      ReentrantLock is better than Synchronized here

    Features unique to ReentrantLock:

    1. ReentrantLock can specify a fair lock or an unfair lock. Synchronized can only use unfair locks. Fair lock means that the thread waiting first gets the lock first
    2. Provides a Condition class that can wake up threads that need to be woken up in groups. Synchronized can only wake up one thread randomly, or wake up all threads.
    3. Provides a mechanism to interrupt threads waiting for locks, lock.lockInterruptily(). The ReentrantLock implementation is a spin lock that implements locking by calling the CAS operation. The performance is better because it avoids the blocking state of the thread entering the kernel state.
    4. In general, ReentrantLock can do everything Synchronized can do. In terms of performance, ReentrantLock is better than Synchronized

    Synchronized advantages:

    1. There is no need to manually release the lock, the JVM handles it automatically. If an exception occurs, the JVM will automatically release the lock.
    2. When the JVM performs lock management requests and releases, the JVM is able to generate lock information when generating thread dumps. This information is very useful for debugging because they can identify sources of deadlocks and other abnormal behavior. ReentrantLock is just an ordinary class. The JVM does not know which thread owns the lock.
    3. Synchronized can be used on all versions of JVM. ReentrantLock may not be supported by some JVMs before version 1.5.

    Description of some methods in ReentrantLock:

    1. boolean tryLock(): Acquire the lock only if the lock is not held by another thread at the time of calling
    2. boolean tryLock(long timeout, TimeUnit unit): Acquire this lock if the lock is not held by another thread at the given time and the current thread is not interrupted
    3. void lockInterruptibly(): If the current thread is not interrupted, acquire the lock; if it is interrupted, throw an exception
    4. boolean isLocked(): Query whether this lock is held by any thread
    5. boolean isHeldByCurrentThread(): Query whether the current thread remains locked
    6. boolean isFair(): Determine whether it is a fair lock
    7. boolean hasQueuedThread(Thread thread): Query whether the specified thread is waiting to acquire this lock
    8. boolean hasQueuedThreads(): Are there threads waiting to acquire this lock?
    9. int getHoldCount(): Query the number of locks held by the current thread
  • code example

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j
    public class LockExample {
          
          
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        public static int count = 0;
        private static final Lock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
          
          
                executorService.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
          
          
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
          
          
            lock.lock();
            try {
          
          
                count++;
            } finally {
          
          
                lock.unlock();
            }
        }
    }
    

ReentrantReadWriteLock

  • Overview

    Read-write locks can only acquire write locks when there are no read locks; if the write lock cannot be acquired, it will lead to write lock starvation;

    In scenarios where there is more reading and less writing, the performance of ReentrantReadWriteLock is much higher than that of ReentrantLock. It does not affect each other during multi-threaded reading. Unlike ReentrantLock, even multi-threaded reading requires each thread to acquire a read lock; however, when any thread is writing , similar to ReentrantLock, no matter whether other threads are reading or writing, they must acquire the write lock. It should be noted that the same thread can hold read locks and write locks at the same time.

  • code example

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j
    public class LockExample {
          
          
        private final Map<String, Data> map = new TreeMap<>();
        private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        private final Lock readLock = lock.readLock();
        private final Lock writeLock = lock.writeLock();
    
        public Data get(String key) {
          
          
            readLock.lock();
            try {
          
          
                return map.get(key);
            } finally {
          
          
                readLock.unlock();
            }
        }
    
        public Set<String> getAllKeys() {
          
          
            readLock.lock();
            try {
          
          
                return map.keySet();
            } finally {
          
          
                readLock.unlock();
            }
        }
    
        public Data put(String key, Data value) {
          
          
            writeLock.lock();
            try {
          
          
                return map.put(key, value);
            } finally {
          
          
                writeLock.unlock();
            }
        }
    
        class Data {
          
          
    
        }
    }
    

StampedLock

  • Overview

StampedLock is the implementation of ReentrantReadWriteLock. The main difference is that StampedLock does not allow reentrancy. With the addition of optimistic read function, it will be more complicated to use, but it will have better performance.

StampedLock controls three modes of locks: read, write, optimistic read

The status of StampedLock consists of two parts: version and mode. The lock acquisition method returns a number called a ticket, which uses the corresponding lock status to represent and control related access. The number 0 indicates that the write lock is not authorized to access.

Read locks are divided into pessimistic locks and optimistic locks. Optimistic reads are in scenarios where there is more reading and less writing. Optimists believe that the probability of writing and reading happening at the same time is very small. Therefore, optimistic locks are completely locked with read locks. The program can check whether changes have been made by writing after reading, and then take subsequent measures. Such improvements can greatly improve the throughput of the program.

In short, in scenarios where there are more and more reading threads, StampedLock greatly improves the throughput of the program.

import java.util.concurrent.locks.StampedLock;

class Point {
    
    
    private double x, y;
    private final StampedLock sl = new StampedLock();

    void move(double deltaX, double deltaY) {
    
     // an exclusively locked method
        long stamp = sl.writeLock();
        try {
    
    
            x += deltaX;
            y += deltaY;
        } finally {
    
    
            sl.unlockWrite(stamp);
        }
    }

    //下面看看乐观读锁案例
    double distanceFromOrigin() {
    
     // A read-only method
        long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
        double currentX = x, currentY = y; //将两个字段读入本地局部变量
        if (!sl.validate(stamp)) {
    
     //检查发出乐观读锁后同时是否有其他写锁发生?
            stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁
            try {
    
    
                currentX = x; // 将两个字段读入本地局部变量
                currentY = y; // 将两个字段读入本地局部变量
            } finally {
    
    
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    //下面是悲观读锁案例
    void moveIfAtOrigin(double newX, double newY) {
    
     // upgrade
	// Could instead start with optimistic, not read mode
        long stamp = sl.readLock();
        try {
    
    
            while (x == 0.0 && y == 0.0) {
    
     //循环,检查当前状态是否符合
                long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
                if (ws != 0L) {
    
     //这是确认转为写锁是否成功
                    stamp = ws; //如果成功 替换票据
                    x = newX; //进行状态改变
                    y = newY; //进行状态改变
                    break;
                } else {
    
     //如果不能成功转换为写锁
                    sl.unlockRead(stamp); //我们显式释放读锁
                    stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
                }
            }
        } finally {
    
    
            sl.unlock(stamp); //释放读锁或写锁
        }
    }
}
  • code example

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.StampedLock;
    
    @Slf4j
    public class LockExample {
          
          
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        public static int count = 0;
        private static final StampedLock lock = new StampedLock();
    
        public static void main(String[] args) throws InterruptedException {
          
          
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
          
          
                executorService.execute(() -> {
          
          
                    try {
          
          
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
          
          
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
          
          
    //加锁时返回一个long类型的票据
            long stamp = lock.writeLock();
            try {
          
          
                count++;
            } finally {
          
          
    //释放锁的时候带上加锁时返回的票据
                lock.unlock(stamp);
            }
        }
    }
    

    We can make a preliminary judgment on whether to choose Synchronized or ReentrantLock like this:

    1. Synchronized is a good general-purpose lock implementation when there are only a few competitors
    2. There are many competitors, but the growth trend of threads is predictable. At this time, using ReentrantLock is a good general lock implementation.
    3. Synchronized will not cause deadlock. Improper use of other locks may cause deadlock.

Condition

  • Overview

    Condition is a tool class for coordinating communication between multiple threads. Using it can provide better flexibility. For example, it can realize the multi-channel notification function, that is, in a Lock object, multiple Condition instances can be created, and the thread object can be registered in a specified Condition, so as to selectively perform thread notification and be more flexible in scheduling threads.

  • Features

    1. The premise of Condition is Lock, and the Condition object is created by the newCondition() method in AQS.
    2. The await() method of Condition indicates that the thread is deleted from AQS and releases the lock acquired by the thread; and enters the Condition waiting queue, waiting to be notified.
    3. The signal() method of Condition means waking up the nodes in the Condition waiting queue and preparing to acquire the lock.
  • code example

    package io.binghe.concurrency.example.lock;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    @Slf4j
    public class LockExample {
          
          
        public static void main(String[] args) {
          
          
            ReentrantLock reentrantLock = new ReentrantLock();
            Condition condition = reentrantLock.newCondition();
            new Thread(() -> {
          
          
                try {
          
          
                    reentrantLock.lock();
                    log.info("wait signal"); // 1
                    condition.await();
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                log.info("get signal"); // 4
                reentrantLock.unlock();
            }).start();
            new Thread(() -> {
          
          
                reentrantLock.lock();
                log.info("get lock"); // 2
                try {
          
          
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                condition.signalAll();
                log.info("send signal ~ "); // 3
                reentrantLock.unlock();
            }).start();
        }
    }
    

ThreadLocal

  • Overview

ThreadLocal is provided by JDK and supports thread local variables. This means that the variable stored in ThreadLocal belongs to the current thread, and the variable is isolated from other threads, which means that the variable is unique to the current thread.

If we create a ThreadLocal variable, each thread that accesses this variable will have a local copy of this variable. When multiple threads operate on this variable, they actually operate a local copy of the variable, thus avoiding the need for threads to Security Question

  • Usage example

    Use ThreadLocal to save and print related variable information

    public class ThreadLocalTest {
          
          
        private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        public static void main(String[] args) {
          
          
    		//创建第一个线程
            Thread threadA = new Thread(() -> {
          
          
                threadLocal.set("ThreadA:" + Thread.currentThread().getName());
                System.out.println("线程A本地变量中的值为:" + threadLocal.get());
            });
    		//创建第二个线程
            Thread threadB = new Thread(() -> {
          
          
                threadLocal.set("ThreadB:" + Thread.currentThread().getName());
                System.out.println("线程B本地变量中的值为:" + threadLocal.get());
            });
    		//启动线程A和线程B
            threadA.start();
            threadB.start();
        }
    }
    

    Run the program and the printed information is as follows:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    

    At this time, we add a deletion variable operation for thread A:

    public class ThreadLocalTest {
          
          
        private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
        public static void main(String[] args) {
          
          
    		//创建第一个线程
            Thread threadA = new Thread(() -> {
          
          
                threadLocal.set("ThreadA:" + Thread.currentThread().getName());
                System.out.println("线程A本地变量中的值为:" + threadLocal.get());
                threadLocal.remove();
                System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());
            });
    		//创建第二个线程
            Thread threadB = new Thread(() -> {
          
          
                threadLocal.set("ThreadB:" + Thread.currentThread().getName());
                System.out.println("线程B本地变量中的值为:" + threadLocal.get());
                System.out.println("线程B没有删除本地变量:" + threadLocal.get());
            });
    		//启动线程A和线程B
            threadA.start();
            threadB.start();
        }
    }
    

    The printed information is as follows:

    线程A本地变量中的值为:ThreadAThread-0
    线程B本地变量中的值为:ThreadBThread-1
    线程B没有删除本地变量:ThreadBThread-1
    线程A删除本地变量后ThreadLocal中的值为:null
    

    Through the above program, we can see that the variables stored in ThreadLocal by thread A and thread B do not interfere with each other. The variables stored by thread A can only be accessed by thread A, and the variables stored by thread B can only be accessed by thread B.

  • ThreadLocal principle

    public class Thread implements Runnable {
          
          
        /***********省略N行代码*************/
        ThreadLocal.ThreadLocalMap threadLocals = null;
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    /***********省略N行代码*************/
    }
    

    As can be seen from the above source code, there are threadLocals and inheritableThreadLocals in the ThreadLocal class. Both variables are ThreadLocalMap type variables, and the initial values ​​​​of both are null. Only the first time the current thread calls the set() method or get( ) method will instantiate the variable.

    It should be noted that the local variables of each thread are not stored in the ThreadLocal instance, but in the threadLocals variable of the calling thread; that is, when the set() method of ThreadLocal is called, the stored local variables are stored in In the memory space of the specific calling thread, ThreadLocal only provides get() and set() methods to access local variable values; when the set() method is called, the value to be set is stored in the threadLocals of the calling thread. When calling When the get() method is used to obtain the value, the variables stored in threadLocals will be obtained from the current thread;

    1. set()

      public void set(T value) {
              
              
          //获取当前线程
          Thread t = Thread.currentThread();
          //以当前线程为key,获取ThreadLocalMap对象
          ThreadLocalMap map = getMap(t);
          if (map != null)
              //获取的获取ThreadLocalMap不为空,则赋值操作
              map.set(this, value);
          else
              //获取ThreadLocalMap为空,则为当前线程创建并赋值
              createMap(t, value);
      }
      
      /**
           * Create the map associated with a ThreadLocal. Overridden in
           * InheritableThreadLocal.
           *
           * @param t the current thread
           * @param firstValue value for the initial entry of the map
           */
          void createMap(Thread t, T firstValue) {
              
              
              t.threadLocals = new ThreadLocalMap(this, firstValue);
          }
      
    2. get()

      /**
       * Returns the value in the current thread's copy of this
       * thread-local variable.  If the variable has no value for the
       * current thread, it is first initialized to the value returned
       * by an invocation of the {@link #initialValue} method.
       *
       * @return the current thread's value of this thread-local
       */
      public T get() {
              
              
          //获取当前线程
          Thread t = Thread.currentThread();
          //获取当前线程ThreadLocalMap
          ThreadLocalMap map = getMap(t);
          if (map != null) {
              
              
              //ThreadLocalMap不为空,则从中取值
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null) {
              
              
                  @SuppressWarnings("unchecked")
                  T result = (T)e.value;
                  return result;
              }
          }
          //ThreadLocalMap为空,则返回默认值
          return setInitialValue();
      }
      
    3. remove()

      public void remove() {
              
              
          //获取当前线程中的threadLocals
          ThreadLocalMap m = getMap(Thread.currentThread());
          if (m != null)
              //若threadLocals不为空,则清除
              m.remove(this);
      }
      

      Note: If the thread never terminates, the local variables will always exist in the ThreadLocal of the calling thread; therefore, if the local variables are not needed, you can call the remove() method of ThreadLocal to delete them to avoid memory overflow problems.

  • ThreadLocal variables are not transitive

    Local variables stored using ThreadLocal are not transitive; that is to say, for the same ThreadLocal, after setting the value in the parent thread, the value cannot be obtained in the child thread;

    public class ThreadLocalTest {
          
          
            private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
    
            public static void main(String[] args) {
          
          
    			//在主线程中设置值
                threadLocal.set("ThreadLocalTest");
    			//在子线程中获取值
                Thread thread = new Thread(new Runnable() {
          
          
                    @Override
                    public void run() {
          
          
                        System.out.println("子线程获取值:" + threadLocal.get());
                    }
                });
    			//启动子线程
                thread.start();
    			//在主线程中获取值
                System.out.println("主线程获取值:" + threadLocal.get());
            }
        }
    

    After running the above field code, the results look like this:

    主线程获取值:ThreadLocalTest
    子线程获取值:null
    

    As can be seen from the above example, after setting the value of ThreadLocal in the parent thread, the value cannot be obtained in the child thread;

    Is there any way to get the value of the parent thread in the child thread? We can achieve this through InheritableThreadLocal;

  • InheritableThreadLocal

    The InheritableThreadLocal class inherits from ThreadLocal, which can obtain the value set in the parent thread in the child thread;

    public class ThreadLocalTest {
          
          
            private static InheritableThreadLocal<String> threadLocal = 
                new InheritableThreadLocal<String>();
    
            public static void main(String[] args) {
          
          
    			//在主线程中设置值
                threadLocal.set("ThreadLocalTest");
    			//在子线程中获取值
                Thread thread = new Thread(new Runnable() {
          
          
                    @Override
                    public void run() {
          
          
                        System.out.println("子线程获取值:" + threadLocal.get());
                    }
                });
    			//启动子线程
                thread.start();
    			//在主线程中获取值
                System.out.println("主线程获取值:" + threadLocal.get());
            }
        }
    

    Running the above program, the results are as follows:

    主线程获取值:ThreadLocalTest
    子线程获取值:ThreadLocalTest
    

    It can be seen that using InheritableThreadLocal, the child thread can obtain the local variables set in the parent thread;

  • InheritableThreadLocal principle

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
          
          
        
        protected T childValue(T parentValue) {
          
          
            return parentValue;
        }
    
        /**
         * Get the map associated with a ThreadLocal.
         *
         * @param t the current thread
         */
        ThreadLocalMap getMap(Thread t) {
          
          
           return t.inheritableThreadLocals;
        }
    
        /**
         * Create the map associated with a ThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the table.
         */
        void createMap(Thread t, T firstValue) {
          
          
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    

    It can be seen from the source code that InheritableThreadLocal inherits from ThreadLocal and overrides the childValue()/getMap()/createMap() method.

    That is to say, when the set() method of ThreadLocal is called, the inheritableThreadLocals variable of the current thread is created instead of the threadLocals variable; at this time, if the parent thread creates a child thread, in the constructor of the Thread class, the parent thread's The local variables in the inheritableThreadLocals variable are copied to the inheritableThreadLocals of the child thread.

Concurrency issues

Visibility issues

Visibility problem, that is, if one thread modifies a shared variable, another thread cannot see the modification immediately. This is caused by the CPU adding cache;

Single-core CPUs do not have visibility problems, only multi-core CPUs have visibility problems; single-core CPUs actually execute serially due to single-core time slice scheduling; while multi-cores can achieve parallelism;

Visibility sample code:

public class ConcurrentTest {
    
    

    private int num = 0;

    public static void main(String[] args) throws InterruptedException {
    
    
        ConcurrentTest concurrentTest = new ConcurrentTest();
        concurrentTest.threadsTest();
    }

    public void threadsTest() throws InterruptedException {
    
    
        Thread thread = new Thread("test-1") {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 20; i++) {
    
    
                    try {
    
    
                        addNum();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread2 = new Thread("test-2") {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 20; i++) {
    
    
                    try {
    
    
                        addNum();

                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };

        thread.start();
        thread2.start();

        thread.join();
        thread2.join();
        System.out.println("执行完毕");
    }

    private void addNum() throws InterruptedException {
    
    
        Thread.sleep(1000);
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}

atomicity problem

Atomicity refers to the characteristic that one or more operations are not interrupted during CPU execution. Once an atomic operation starts running, it will continue until the end of the operation without interruption.

Atomic problems refer to the situation where one or more operations are interrupted during the execution of the CPU;

When a thread is performing an operation, the CPU switches to perform other tasks, causing the current task to be interrupted, which will cause atomicity problems;

In JAVA, concurrent programs are written based on multi-threading, which also involves the problem of CPU switching between threads. It is precisely because of the CPU switching between threads that atomicity problems may occur in concurrent programming;

ordering problem

Orderliness means: code is executed in the order of execution

Instruction reordering: In order to optimize program performance, the compiler or interpreter sometimes modifies the execution order of the program, but this modification may cause unexpected problems;

In a single-threaded case, instruction reordering can still ensure that the final result is consistent with the program's sequential execution result, but in a multi-threaded case, there may be problems;

Orderliness problem: In order to optimize the program, the CPU reorders instructions. At this time, the execution order may be inconsistent with the encoding order, which may cause orderliness problems;

Summarize

There are three main reasons why concurrent programming may cause problems: visibility problems caused by cache, atomicity problems caused by CPU thread switching, and ordering problems caused by performance optimization instruction reordering.

Happens-Before principle

In JDK1.5 version, the JAVA memory model introduces the Happens-Before principle

Sample code 1:

class VolatileExample {
    
    
    int x = 0;
    volatile boolean v = false;

    public void writer() {
    
    
        x = 1;
        v = true;
    }

    public void reader() {
    
    
        if (v == true) {
    
    
            //x的值是多少呢?
        }
    }
}
  1. program order rules

    ​ In a single thread, according to the code order, the previous operation happens-before any subsequent operation.

    ​ For example: In Example 1, the code x = 1 will be completed before v = true;

  2. Volatile variable rules

    For a volatile write operation, Happens-Before subsequent read operations on it

  3. Delivery rules

    ​ If A Happens-Before B, B Happens-Before C, then there is A Happens-Before C

    Combining principles 1, 2, 3 and sample code 1, we can draw the following conclusions:

    • x = 1 Happens-Before v = true, consistent with principle 1;
    • Write variable v = true Happens-Before Read variable v = true, consistent with principle 2
    • Then according to principle 3, x = 1 Happens-Before reading variable v = true;
    • That is to say, if thread B reads v = true, then x = 1 set by thread A is visible to B, which means that thread B at this time can access x = 1
  4. Locking rules

    The unlocking operation of a lock happens-before the subsequent locking operation of the lock.

  5. Thread startup rules

    ​ If thread A calls start() of thread B to start thread B, then the start() method happens-before any operation in thread B.

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{
          
          
        //此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  6. Thread termination rules

    ​ Thread A waits for thread B to complete (call thread B's join() method). When thread B completes (thread B's join() method returns), thread A can access thread B's modification of the shared variable.

    Thread threadB = new Thread(()-{
          
          
    //在线程B中,将共享变量x的值修改为100 
            x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  7. Thread interruption rules

    ​ A call to the thread's interrupt() method happens-before the interrupted thread detects that the interrupt event occurs.

  8. Object finalization rules

    ​ Happens-Before after the initialization of an object is completed at the beginning of the object's finilize() method

ForkJoin framework

  • Overview

    Java 1.7 introduces a new concurrency framework - the Fork/Join framework; it is mainly used to implement the "divide and conquer" algorithm, especially the functions that are called recursively after divide and conquer.

    The essence of the Fork/Join framework is a framework for executing tasks in parallel, which can divide a large task into multiple small tasks, and finally aggregate the results of each small task to obtain the results of the large task. The Fork/Join framework coexists with ThreadPool and is not intended to replace ThreadPool.

  • principle

    Fork/Join uses an infinite queue to save the tasks that need to be executed, and the number of threads is passed in through the constructor. If the number of threads is not passed in, the number of CPUs available on the current computer will be set to the number of threads by default.

    ForkjoinPool mainly uses the divide and conquer method to solve the problem. Typical applications include quick sort algorithm.

  • Framework implementation

    1. ForkJoinPool

      Implemented the thread pool in the ForkJoin framework

    2. ForkJoinWorkerThread

      Implement threads in the ForkJoin framework

    3. ForkJoinTask

      It encapsulates data and its corresponding operations, and supports fine-grained data parallelism.

      ForkJoinTask mainly includes two methods, fork() and join(), to realize the splitting and merging of tasks respectively;

      The fork() method is similar to the start() method in the Thread method, but it does not execute the task immediately, but puts the task into the execution queue.

      The join() method is different from Thread's join() method. It does not simply block the thread; instead, it uses the worker thread to perform other tasks. When a worker thread calls join(), it will handle other tasks until it notices the child thread. Execution complete.

    4. RecursiveTask

      ForkJoinTask that returns results implements Callable

    5. RecursiveAction

      ForkJoinTask without return result implements Runnalbe

    6. CountedCompleter

      After the task execution is completed, a custom hook function will be triggered to execute.

  • Sample code

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {
          
          
        public static final int threshold = 2;
        private int start;
        private int end;
    
        public ForkJoinTaskExample(int start, int end) {
          
          
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
          
          
            int sum = 0;
    		//如果任务足够小就计算任务
            boolean canCompute = (end - start) <= threshold;
            if (canCompute) {
          
          
                for (int i = start; i <= end; i++) {
          
          
                    sum += i;
                }
            } else {
          
          
    			// 如果任务大于阈值,就分裂成两个子任务计算
                int middle = (start + end) / 2;
                ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
                ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
    			// 执行子任务
                leftTask.fork();
                rightTask.fork();
    			// 等待任务执行结束合并其结果
                int leftResult = leftTask.join();
                int rightResult = rightTask.join();
    			// 合并子任务
                sum = leftResult + rightResult;
            }
            return sum;
        }
    
        public static void main(String[] args) {
          
          
            ForkJoinPool forkjoinPool = new ForkJoinPool();
    		//生成一个计算任务,计算1+2+3+4
            ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
    		//执行一个任务
            Future<Integer> result = forkjoinPool.submit(task);
            try {
          
          
                log.info("result:{}", result.get());
            } catch (Exception e) {
          
          
                log.error("exception", e);
            }
        }
    }
    

completed before v = true;

  1. Volatile variable rules

    For a volatile write operation, Happens-Before subsequent read operations on it

  2. Delivery rules

    ​ If A Happens-Before B, B Happens-Before C, then there is A Happens-Before C

    Combining principles 1, 2, 3 and sample code 1, we can draw the following conclusions:

    • x = 1 Happens-Before v = true, consistent with principle 1;
    • Write variable v = true Happens-Before Read variable v = true, consistent with principle 2
    • Then according to principle 3, x = 1 Happens-Before reading variable v = true;
    • That is to say, if thread B reads v = true, then x = 1 set by thread A is visible to B, which means that thread B at this time can access x = 1
  3. Locking rules

    The unlocking operation of a lock happens-before the subsequent locking operation of the lock.

  4. Thread startup rules

    ​ If thread A calls start() of thread B to start thread B, then the start() method happens-before any operation in thread B.

    //在线程A中初始化线程B
    Thread threadB = new Thread(()->{
          
          
        //此处的变量x的值是多少呢?答案是100
    });
    //线程A在启动线程B之前将共享变量x的值修改为100
    x = 100;
    //启动线程B
    threadB.start();
    
  5. Thread termination rules

    ​ Thread A waits for thread B to complete (call thread B's join() method). When thread B completes (thread B's join() method returns), thread A can access thread B's modification of the shared variable.

    Thread threadB = new Thread(()-{
          
          
    //在线程B中,将共享变量x的值修改为100 
            x = 100;
    });
    //在线程A中启动线程B
    threadB.start();
    //在线程A中等待线程B执行完成
    threadB.join();
    //此处访问共享变量x的值为100
    
  6. Thread interruption rules

    ​ A call to the thread's interrupt() method happens-before the interrupted thread detects that the interrupt event occurs.

  7. Object finalization rules

    ​ Happens-Before after the initialization of an object is completed at the beginning of the object's finilize() method

ForkJoin framework

  • Overview

    Java 1.7 introduces a new concurrency framework - the Fork/Join framework; it is mainly used to implement the "divide and conquer" algorithm, especially the functions that are called recursively after divide and conquer.

    The essence of the Fork/Join framework is a framework for executing tasks in parallel, which can divide a large task into multiple small tasks, and finally aggregate the results of each small task to obtain the results of the large task. The Fork/Join framework coexists with ThreadPool and is not intended to replace ThreadPool.

  • principle

    Fork/Join uses an infinite queue to save the tasks that need to be executed, and the number of threads is passed in through the constructor. If the number of threads is not passed in, the number of CPUs available on the current computer will be set to the number of threads by default.

    ForkjoinPool mainly uses the divide and conquer method to solve the problem. Typical applications include quick sort algorithm.

  • Framework implementation

    1. ForkJoinPool

      Implemented the thread pool in the ForkJoin framework

    2. ForkJoinWorkerThread

      Implement threads in the ForkJoin framework

    3. ForkJoinTask

      It encapsulates data and its corresponding operations, and supports fine-grained data parallelism.

      ForkJoinTask mainly includes two methods, fork() and join(), to realize the splitting and merging of tasks respectively;

      The fork() method is similar to the start() method in the Thread method, but it does not execute the task immediately, but puts the task into the execution queue.

      The join() method is different from Thread's join() method. It does not simply block the thread; instead, it uses the worker thread to perform other tasks. When a worker thread calls join(), it will handle other tasks until it notices the child thread. Execution complete.

    4. RecursiveTask

      ForkJoinTask that returns results implements Callable

    5. RecursiveAction

      ForkJoinTask without return result implements Runnalbe

    6. CountedCompleter

      After the task execution is completed, a custom hook function will be triggered to execute.

  • Sample code

    public class ForkJoinTaskExample extends RecursiveTask<Integer> {
          
          
        public static final int threshold = 2;
        private int start;
        private int end;
    
        public ForkJoinTaskExample(int start, int end) {
          
          
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
          
          
            int sum = 0;
    		//如果任务足够小就计算任务
            boolean canCompute = (end - start) <= threshold;
            if (canCompute) {
          
          
                for (int i = start; i <= end; i++) {
          
          
                    sum += i;
                }
            } else {
          
          
    			// 如果任务大于阈值,就分裂成两个子任务计算
                int middle = (start + end) / 2;
                ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
                ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
    			// 执行子任务
                leftTask.fork();
                rightTask.fork();
    			// 等待任务执行结束合并其结果
                int leftResult = leftTask.join();
                int rightResult = rightTask.join();
    			// 合并子任务
                sum = leftResult + rightResult;
            }
            return sum;
        }
    
        public static void main(String[] args) {
          
          
            ForkJoinPool forkjoinPool = new ForkJoinPool();
    		//生成一个计算任务,计算1+2+3+4
            ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
    		//执行一个任务
            Future<Integer> result = forkjoinPool.submit(task);
            try {
          
          
                log.info("result:{}", result.get());
            } catch (Exception e) {
          
          
                log.error("exception", e);
            }
        }
    }
    

Guess you like

Origin blog.csdn.net/weixin_40709965/article/details/128160545