This article is a summary of the basic knowledge of concurrent programming, without advanced usage, to consolidate the foundation and enhance the memory of knowledge points. So it can be viewed as a manuscript for those of you who are familiar with the basic concepts.

Basic concept

1) The relationship between CPU cores and threads

Number of cores: number of threads =1:1; With hyperthreading =1:2

2) CPU time slice rotation mechanism

Also known as RR scheduling, it causes context switching

3) What are processes and threads?

Process: The smallest unit of resources allocated for a program to run. There are multiple threads in a process that share the process’s resources

Thread: The smallest unit of CPU scheduling that must depend on the process.

4) Why use concurrent programming?

Benefits: make full use of CPU resources, speed up the user response time, program modular, asynchronous

Problem: Threads share resources and conflict with each other. Easy to cause deadlock; Enabling too many threads has the potential to crash the machine

A few concepts you need to know

1) Synchronous and asynchronous?

Synchronous method once invoked, the caller must wait until the method returns before continuing with subsequent behavior.

Asynchronous method invocation is more like messaging; once started, the method invocation returns immediately and the caller can immediately continue with subsequent operations. Asynchronous methods are usually executed in a different thread.

2) Concurrency versus parallelism?

Strictly speaking: parallel multiple tasks are really being executed at the same time, and true parallelism can only occur on systems with multiple cpus (such as multicore cpus).

In the case of concurrency, multiple tasks are executed alternately, with the system constantly switching between tasks, but appearing in parallel to an outside observer. In addition, concurrency is more concerned with the ability to process things per unit of time.

3) Critical region?

A critical section represents a common resource or shared data that can be used by multiple threads, but only one thread at a time. Once a critical section resource is occupied, other threads can only wait. In parallel programs, critical section resources are protected, and if they are not protected properly, the results may not be needed by the thread.

4) Blocking and non-blocking?

Blocking and non-blocking are commonly used to describe the interaction of multiple threads. For example, if one thread occupies a critical area resource, all other threads that need this resource must wait in the critical area, that is, the thread hangs, which is called blocking. If the hogging thread does not release the resource, other threads blocking in the critical section will wait.

Non-blocking means the opposite.

5) Deadlocks, live locks, starvation?

Deadlocks are probably the worst case of concurrent programming, usually because two threads are holding on to each other’s resources without releasing them, causing each other to wait indefinitely for the resources to be released by the other.

Tips: If you want to avoid deadlocks, you can use lock-free functions. You can also use reentrant locks.

Live lock: threads give in to each other and give up resources to each other (two people use the elevator together, one person wants to get in, one person wants to get out, and keep giving places to each other). A live lock is a situation in which resources bounce between threads so that no thread can hold all resources at the same time.

Hunger: It could be that the thread has a low priority or a critical resource has been occupied so that it cannot execute. Hunger is not as serious as deadlock and may be resolved in the future.

6) Concurrency level

  • blocking

    (synchronized)

  • There is no hunger

    (Unfair lock allows threads with higher priority to jump the queue, while threads with lower priority will starve)

  • barrier-free

    (Multiple threads enter the critical section at the same time, make modifications, and if problems occur, roll back and try again; One possible implementation is to rely on the consistency tag, which is read and saved before the operation and checked to see if the tag has changed after the operation. Retry if inconsistent)

  • unlocked

    (Requirement: A thread can complete operations in a limited number of steps. Infinite loop CAS, some unlucky threads will starve due to too many retries.

  • Without waiting for

    Requirement: All threads can complete operations in a limited number of steps. A typical implementation is RCU[Read Copy Update]. The basic idea is that data is Read without control. However, when writing data, it takes a copy of the original data first, and then modifies the copy. This is also the reason why the read can be uncontrolled and the data can be written back when the modification is done.)

Learn about threads in Java

1) Thread state?

There are five: New, ready, blocked, Running, and dead

Threads in wait state are blocked when they are called wait() or sleep(), and re-enter ready state when they are notified () or notifyAll(). InterruptedException is thrown when a blocked thread is interrupted.

2) Three ways to start a thread?

  • Implement the Runnable interface
  • Thread class inheritance
  • Implement Callable interface (with return value)

3) How to stop threads safely?

The task is automatically terminated when it is complete or stops when an unknown exception occurs.

  • mandatory

The JDK provides some class-force stop methods that can be roughly interpreted as terminating a process in the task manager. However, these designs are no longer recommended because they are not good:

Stop (), resume(), and suspend() are not recommended. Stop () causes threads to release resources incorrectly, and suspend() may cause deadlocks.

  • Cooperative stop

Use the interrupt() method of the thread. Calling an interrupt() method on a thread to interrupt a thread does not force it to shut down. It simply says hello to the thread and sets its interrupt flag to true. Whether the thread interrupts is up to the thread.

IsInterrupted () checks whether the current thread isInterrupted.

The thread’s interrupt flag is reset to false if InterruptedException is thrown. If we do interrupt the thread, we need to call interrupt() again in the catch block ourselves.

The static method thread.interrupted () checks whether the current Thread is interrupted and the interrupt flag is set to false. Use this method with caution.

A well-designed response interrupt paradigm:

public class RunnableTask implements Runnable {



 @Override

 public void run(a) {



  // This can be used if the thread itself requires polling execution

  while(! Thread.currentThread().isInterrupted()) {

   System.out.println("RunnableTask working...");

   try {

    TimeUnit.SECONDS.sleep(1);

   } catch (InterruptedException e) {

    e.printStackTrace();

    Thread.currentThread().interrupt();

    // Static method, whether to break the state, note: this method will be re-marked as false

// System.out.println(Thread.interrupted());

// Thread.currentThread().interrupt();

   }

  }



  // If it is executed only once, then the flag bit is determined when the final interrupt is needed

//  if (!Thread.currentThread().isInterrupted()) {

// System.out.println("RunnableTask working..." );

// try {

// TimeUnit.SECONDS.sleep(1);

// } catch (InterruptedException e) {

// e.printStackTrace();

// Thread.currentThread().interrupt();

/ /}

/ /}

 }



}

Copy the code

4) Other concepts

Run () and start() : The run methods are ordinary methods of ordinary objects, and Java does not execute run methods until start() is called, mapping thread objects to actual threads in the operating system.

Yield () : Yield execution of a thread from running to runnable, but the thread may be selected to run again at the next time slice.

Priority of the thread

The value ranges from 1 to 10, and the default value is 5. However, the priority of the thread is unreliable. Therefore, it is not recommended for thread development. Because it is possible to have Windows operating system support, but switch to Linux will not work properly.

Daemon thread

In conjunction with the main thread, finally is not guaranteed to execute.

Join () : Generally used to control the order in which threads are executed. thread.join(); That is, the thread will execute before the current thread task. The implementation principle is to continuously check whether the join thread is alive. If it is alive, wait for wait(0), and call notifyAll() when it finishes. The common usage is to call Join () to pass a thread into a specific task run(). Note that join() is implemented by wait() and notify(), so you should avoid using wait notification to avoid exceptions.

5) Sharing between threads

Synchronized Built-in lock, which can lock objects and classes

  • An object lock locks an object instance of a class.

  • A Class lock locks the Class object of each Class. Each Class has only one Class object in a VM, so there is only one Class lock.

Synchronized is a class lock if the method used is static. Note that the currently held lock is released when an exception occurs.

  • volatile

It is suitable for scenarios where only one thread writes and multiple threads read because it only ensures visibility.

  • ThreadLocal

Thread variables. Map<Thread, Object>

6) Collaboration between threads

With wait notification, there is a standard paradigm:

Wait for the party:

1. Obtain the lock of the object. Check whether the conditions are met. If not, call wait method 3. Conditions are met to execute the business logic

Notify party:

1. Acquire the lock of the object 2. Change the condition 3. Notify all threads waiting on the object (note: notification should be done at the end of the critical section, usually at the end of the code block)

Who should notify and notifyAll be used? Use notifyAll whenever possible. Use notify because signal loss is possible because it is a random thread that holds the same lock.

7) callYield (), sleep(), wait(), notify()What are the effects of lock and other methods?

A lock held by a thread after yield() is not released.

After the sleep() method is called, the held lock is not released.

Before invoking a method, you must hold the lock. After the wait() method is called, the lock is released (automatically by the virtual machine), and when the wait() method returns (notified to continue), the thread reholds the lock.

Before transferring a method, a lock must be held. Calling notify() and notifyAll() does not release the lock itself, but generally releases the lock automatically when exiting the synchronized area. It is usually written on the line above the exit braces to ensure logical continuity.