[Multithreading]-The performance bottleneck of concurrent programming (CPU context switching and resource limitation)

Preface

The relevant content in "Java Multithreaded Programming Core Technology" has been updated almost. Next, I will review the valuable knowledge points in "The Art of Java Concurrent Programming" and share the link of the e-book by the way. The series of articles and e-books are better for learning together.

The art of concurrent programming in Java
Link: https://pan.baidu.com/s/18H60E_8KDO9uNIuWdghzcg
Extraction code: 2d8k

1. The bottleneck of concurrent programming

In daily development, not all problems can be solved by parallelism. In many cases, the efficiency of parallelism may not be as high as serial. For example, we write a simple Demo:

public class ContextSwitchDemo_01 {
    
    

	public static final long MAX = 10_000;

	// 并行
	public static void concurrent() {
    
    
		try {
    
    
			// 获取系统当前时间
			long currentTimeMillis = System.currentTimeMillis();
			Thread thread = new Thread(() -> {
    
    
				int a = 0;
				for (int i = 0; i < MAX; i++) {
    
    
					a += 5;
				}
			}, "测试线程一");
			thread.start();
			int b = 0;
			for (int i = 0; i < MAX; i++) {
    
    
				b--;
			}
			thread.join();
			long currentTimeMillis2 = System.currentTimeMillis();
			System.out.println("并行方法运行共需时间:" + (currentTimeMillis2 - currentTimeMillis)+"毫秒");
		} catch (InterruptedException e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	// 串行
	public static void serial() {
    
    
		long currentTimeMillis = System.currentTimeMillis();
		int a = 0;
		for (int i = 0; i < MAX; i++) {
    
    
			a += 5;
		}
		int b = 0;
		for (int i = 0; i < MAX; i++) {
    
    
			b--;
		}
		long currentTimeMillis2 = System.currentTimeMillis();
		System.out.println("串行方法运行共需时间:" + (currentTimeMillis2 - currentTimeMillis)+"毫秒");
	}
	// 测试入口
	public static void main(String[] args) {
    
    
		concurrent();
		serial();
	}
}

Let's observe the results of the operation:
Insert picture description here
here the two threads are processing the two data a and b respectively, and the number of loops is the same, but the final running time is serial processing less than parallel processing, why is this? ? In fact, this is mainly due to two reasons:

  1. CPU context switching
  2. Time overhead caused by creating a new thread (resource limit)

Two, CPU context introduction

1. What is the context of CPU

To understand what the context of the CPU is, we must first understand what the main components of the CPU are:
Insert picture description here
we must first know that no matter how complex business processing we do in the code, the CPU only does three things at the hardware level:

  1. Read data in memory or cache
  2. Perform operations on the read data
  3. Write data to memory or cache

When we have multiple processes and threads processing business at the same time, although in our perception all businesses are performed at the same time, in fact, the computing unit in the CPU can only process one instruction per unit time. In order to make us perceptually feel that the CPU is doing multiple business processing at the same time, we need the CPU to have fast computing power and constantly switch between multiple thread instructions, so that the computing unit will be covered with rain and dew in a short time. To process instructions in different threads causes concurrency perceived by users. Switching between multiple threads is inseparable from two hardware, one is the register used to store instructions, and the other is the program counter that is responsible for recording where the CPU operates. The register and the program counter are added together to be the necessary context for the CPU to process instructions.

2. Context switch

The operating system is divided into three types for context switching according to different scenarios:

  1. Process context switch
  2. Thread context switch
  3. Interrupt context switch

Although the scene is different, the essence of context switching means that the CPU saves the state of the current task processing (context information is saved), and then loads a new task for processing (loads and switches to the new context).

3. How to reduce context switching

There are three main methods we can use in Java:

  1. Try to avoid using locks in concurrent programming. When multiple threads compete for locks, it will cause context switching. We can avoid using locks in many ways, such as algorithm matching thread IDs, and different threads processing data in different data segments.
  2. CAS algorithm (compare and switch): CAS algorithm is a method of comparing and judging shared variables. Updating data through CAS algorithm can effectively optimize the use of locks.
  3. Use the minimum number of threads to process tasks, avoid creating redundant threads to cause context switching caused by thread preemption.
  4. Of course, we can also use coroutines, but the Java language itself does not support the implementation of coroutines and needs to call the tripartite framework.

Later, I will write a blog to explain the CAS algorithm.

Three, resource constraints

1. What are resource limits

In fact, the resource limit is well understood. For example, the bandwidth operator at home allocates 1m/s. At this time, we want to download some love action movies to supplement our knowledge (zi) and knowledge (shi). If we unify the time It only takes about 100s to download a 100m movie, but if we download 10 100m movies at the same time, it may take 1000s to be able to see it. For those who are anxious to learn, we definitely cannot bear it. The analogy to the operating system means that when we are processing tasks, we will be affected by objective factors (such as bandwidth, hard disk read and write speed, CPU processing speed, etc.). When we are processing tasks, we need to consider these objective factors for the completion of our tasks. limits.

2. If you avoid resource constraints

If we understand what resource constraints are, we can actually solve them very simply.

  1. The first point is definitely adding money! ! !
    We can avoid resource constraints by solving the constraints of objective factors, such as building clusters and increasing bandwidth. When the constraint domain of objective factors increases, the bottleneck of resource constraints will be resolved to a certain extent.
  2. What if we have no money?
    Sometimes our company definitely cannot allow us to burn money to solve the problem, then we must consider the problem of resource reuse at this time, for example, we can avoid the resource overhead caused by excessive creation of threads through the thread pool.

Four, deadlock

If the problems mentioned above are all at the software level, then the deadlock problem is definitely caused by human factors at the hardware level. We can simply implement a deadlock first:

public class DeadLockDemo_01 {
    
    

	private Object lockA = new Object();
	private Object lockB = new Object();

	public void lockA() throws InterruptedException {
    
    
		System.out.println("lockA方法运行!");
		synchronized (lockA) {
    
    
			Thread.sleep(2_000);
			synchronized (lockB) {
    
    
			}
		}
		System.out.println("lockA方法结束!");
	}

	public void deadLock() throws InterruptedException {
    
    
		System.out.println("deadLock方法运行!");
		Thread.sleep(1_000);
		synchronized (lockB) {
    
    
			synchronized (lockA) {
    
    
			}
		}
		System.out.println("deadLock方法结束!");
	}

	public static void main(String[] args) throws InterruptedException {
    
    
		DeadLockDemo_01 deadLockDemo_01 = new DeadLockDemo_01();

		new Thread(() -> {
    
    
			try {
    
    
				deadLockDemo_01.lockA();
			} catch (InterruptedException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}).start();

		new Thread(() -> {
    
    
			try {
    
    
				deadLockDemo_01.deadLock();
			} catch (InterruptedException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}).start();

	}
}

Operation result:
Insert picture description here
Here, the deadlock is caused by the two methods waiting for each other to release the lock. This phenomenon is catastrophic in production. In order to avoid deadlocks, we must ensure the following:

  1. Avoid holding multiple locks by the same thread
  2. Avoid the same thread scheduling multiple resources in one lock
  3. You can use more time locks and sniffing locks
  4. For database connections, ensure that the lock command and unlock command are issued by the same connection

At this point, today's content is over. I hope students who have gained can like it and add a favorite to encourage the author to continue the codeword.
good luck!

Guess you like

Origin blog.csdn.net/xiaoai1994/article/details/111215620