Some common usage specifications of multi-threaded concurrency

Table of contents

1. Multi-thread concurrent use specification

1.1 Specify thread name

2. Try to use the thread pool

3. Executors are not allowed

4. Properly stop the thread

5. Writing a Stoppable Runnable

6. All exceptions must be caught in Runnable

 7. Consider using ThreadLocal

8. Shortening lock 

9. Choose separate locks, scattered locks or even lock-free data structures

10. Recommendation] Avoid locks based on ThreadLocal

11. Avoid deadlock risk

12. Correct use of volatile modifiers and AtomicXX series

13. The correct way to write delayed initialization


1. Multi-thread concurrent use specification

1.1 Specify the thread name

[Mandatory] When creating a thread or thread pool, please specify a meaningful thread name to facilitate backtracking when errors occur.

  1. Specify the thread name directly when creating a single thread

      Thread thread = new Thread();
      thread.setName("a");

     2. The thread pool uses guava or self-encapsulated ThreadFactory to specify the naming rules.

2. Try to use the thread pool

[Recommendation] Try to use the thread pool to create threads

    Except for special circumstances, try not to create threads by yourself to better protect thread resources. 

    In the same way, the timer should not use Timer, but ScheduledExecutorService should be used . Because the Timer has only a single thread, it cannot execute multiple tasks defined in it concurrently, and if one of the tasks throws an exception, the entire Timer will also hang up, and the ScheduledExecutorService only has the task that does not catch the exception and no longer executes it regularly. Others Missions are not affected. 

3. Executors are not allowed

[Mandatory] Executors are not allowed to be used to create thread pools to avoid the risk of resource exhaustion

Disadvantages of thread pool objects returned by Executors:

  1. FixedThreadPool和SingleThreadPool:

The allowed request queue length is Integer.MAX_VALUE , which may accumulate a large number of requests, resulting in OOM.

  1. CachedThreadPool和ScheduledThreadPool:

The number of threads allowed to be created is Integer.MAX_VALUE, and a large number of threads may be created, resulting in OOM.

The operation rules of the thread pool should be clarified by means of newThreadPoolExecutor(xxx,xxx,xxx,xxx), and the coresize and maxsize of the Queue and thread pool should be set reasonably. It is recommended to use the ThreadPoolBuilder encapsulated by vjkit.

4. Properly stop the thread

[Mandatory] Properly stop threads

Thread.stop () is not recommended. Forced exit is too unsafe, which will lead to incomplete logic and non-atomic operations. It has been defined as a Deprecate method.

To stop a single thread, execute Thread.interrupt().

Stop the thread pool:

ExecutorService.shutdown(): It is not allowed to submit new tasks, wait for the current task and the tasks in the queue to be executed before exiting;

ExecutorService.shutdownNow(): Try to stop all executing threads through Thread.interrupt(), and no longer process tasks still waiting in the queue.

The most elegant way to exit is to execute shutdown() first , and then execute shutdownNow() , which is encapsulated by ThreadPoolUtil of vjkit.

Note that Thread.interrupt() does not guarantee that it can interrupt the running thread, you need to write a Runnable that can be interrupted and exited, see rule 5.

5. Writing a Stoppable Runnable

[Mandatory] Write a stoppable Runnable

When executing Thread.interrupt(), if the thread is in a blocking state such as sleep(), wait(), join(), lock.lockInterruptibly(), etc., an InterruptedException will be thrown. If the thread is not in the above state, the thread state will be set to interrupted.

Therefore, the following code cannot interrupt the thread:


     public void run(){
         while (true){
            sleep();
         }
      }
      public void sleep(){
         try {
            Thread.sleep(1000);
         } catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }
  1. Properly handle InterruptException

        Because InterruptException is a CheckedException that must be handled, the subfunction called by run() can easily eat the exception and simply process it into a log, but this is equivalent to stopping the transmission of the interrupt, and the outer function will not receive the interrupt request. Continue the original cycle or enter the next blockage.

        The correct handling is to call Thread.currentThread().interrupt(); to pass the interrupt out

  public void sleep(){
         try {
            ....
         } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
         }
      }
  1. Before the main loop and entering the blocking state, the thread state must be judged

6. All exceptions must be caught in Runnable

[Mandatory] All exceptions must be caught in Runnable

If the RuntimeException is not caught in the Runnable and thrown out, the following will happen:

  1. ScheduledExecutorService executes a scheduled task, the task will be interrupted, and the task will no longer be scheduled regularly, but the threads in the thread pool can also be used for other tasks.

  2. ExecutorService executes tasks, the current thread will be interrupted, and the thread pool needs to create new threads to respond to subsequent tasks.

  3. If you do not set a custom UncaughtExceptionHanlder in ThreadFactory , the exception will only be printed in System.err, and will not be printed in the project log.

Therefore, it is recommended that self-written Runnables must ensure that exceptions are caught; if it is a third-party Runnable, it can be wrapped with SafeRunnable in vjkit.

 7. Consider using ThreadLocal

[Mandatory] Global non-thread-safe objects can be stored in ThreadLocal

Global variables include singleton objects and static member variables.

Well-known non-thread-safe classes include SimpleDateFormat, Digest of MD5/SHA1 .

These classes need to be created each time they are used.

But if there is a certain cost to create, you can use ThreadLocal to store and reuse.

ThreadLocal variables need to be defined as static and reset before each use.

8. Shortening lock 

【Recommendation】Shortening lock

  1. If you can lock the block, don't lock the entire method body;

  1. If you can use object locks, don't use class locks.

9. Choose separate locks, scattered locks or even lock-free data structures

[ Recommendation] Choose separate locks, scattered locks or even lock-free data structures

  1. Split lock:

1) Read-write separation lock ReentrantReadWriteLock, no lock between read and read, only lock between write and write;

2) The queue of ArrayBase is generally a global lock, while the queue of LinkedBase is generally two locks at the head and tail of the queue.

  1. Scattered locks (also known as segmented locks):

1) For example, ConcurrentHashMap of JDK7 is divided into 16 locks;

2) For counters that are often written and read in a small amount, it is recommended to use the LongAdder object encapsulated by JDK8 or vjkit for better performance (internal scatter into multiple counters, reduce the use of optimistic locks, and add all counters when fetching values)

  1. Lock-free data structures:

1) A completely lock-free and wait-free structure, such as JDK8's ConcurrentHashMap;

2) CAS-based lock-free and waiting data structures, such as the AtomicXXX series.

Avoid locks based on ThreadLocal

10. Recommendation] Avoid locks based on ThreadLocal

For example, although the Random instance is thread-safe, its seed access is actually protected by locks. Therefore, it is recommended to use JDK7's ThreadLocalRandom to avoid locking by placing a seed in each thread.

11. Avoid deadlock risk

[Recommendation] Avoid deadlock risk

The order of locking multiple resources and multiple objects should be consistent.

If you can't be sure to completely avoid deadlock, you can use the tryLock statement with timeout control to lock.

12. Correct use of volatile modifiers and AtomicXX series

[Recommendation] volatile modifier, correct

For objects shared by multiple threads, modifications in a single thread are not guaranteed to be visible to all threads. Using volatile to define variables can be solved (visibility is solved).

But if multiple threads concurrently make modifications based on the current value, such as concurrent counter++, volatile is powerless (it cannot solve atomicity).

At this point the Atomic* series can be used:

But if you need to atomically operate multiple AtomicXXX Counters at the same time, you still need to use synchronized to lock the changed code block.

13. The correct way to write delayed initialization

[Recommendation] The correct way to write delayed initialization

There are hidden dangers in implementing delayed initialization through double-checked locking . It is necessary to declare the target attribute as volatile. For higher performance, the volatile attribute must be assigned to the temporary variable, which is complicated.

So if you just want to simply delay initialization, you can use the following static class method to use the JDK's own class loading mechanism to ensure unique initialization.

Guess you like

Origin blog.csdn.net/XikYu/article/details/131247324