• A critical region
  • Resource racing in critical areas
  • Avoid resource racing
  • Throughput of critical region

The critical section is the section of code in each thread that accesses critical resources, whether hardware or software, which must be mutually exclusive to multiple threads. Resource racing is a special case that can result from no mutually exclusive access while accessing a critical section.

If multiple threads execute in a critical region with different results depending on the order in which the code is executed, we say that there is a race for resources in the critical region.

We will cover resource racing and critical sections in more detail

A critical region

Problems occur when multiple threads access the same resource. For example, access to the same memory area (variables, arrays, or objects), system resources (databases, files, etc.) and, generally speaking, this problem only occurs when multiple threads write to the resource. If it is a simple read operation and the resource is not changed, it will not be a problem. Here is a critical section Java code example that may fail if executed by multiple threads simultaneously: The following critical section of code can cause problems if executed by multiple threads:

public class Counter { protected long count = 0; public void add(long value){ this.count = this.count + value; }}Copy the code

Let’s assume that we have two threads A and B executing add on the same instance. We have no way of knowing when the operating system is switching threads, and the add operation in this code is not atomic to the JVM, but rather follows A similar set of instructions:

  • Load the value of this.count from memory into the register
  • Add operations on registers
  • Writes the value of a register back to memory

Let’s see what happens if we execute in the following order.

   A:  Reads this.count into a register (0)
   B:  Reads this.count into a register (0)
   B:  Adds value 2 to register
   B:  Writes register value (2) back to memory. this.count now equals 2
   A:  Adds value 3 to register
   A:  Writes register value (3) back to memory. this.count now equals 3
Copy the code

If we follow the above execution order, the final result of counter will be 3, but we all know that the final value should be 5, but because the above instructions are cross-executed, the final result will be different.

Resource racing in critical areas

The Add method includes a critical section, and when multiple threads access the critical section, the problem of resource racing occurs. More generally, when two or more threads compete for the same resource, the order in which they are accessed becomes critical. This is the problem of resource racing, and the problem of a block of code causing a resource race is critical section code.

Avoid resource racing

To avoid the problem of resource racing, we must ensure that the code in the critical region must be atomic. This means that when a thread enters a critical section, no other thread can enter it unless that thread leaves the critical section, meaning that only one thread can execute in the critical section at any time.

To avoid the problem of resource racing, you can use the synchronized keyword to synchronize code blocks, or use lock objects, or use atomic variables. We’ll cover that in a follow-up article.

Throughput of critical region

For small critical sections, we can avoid resource racing by simply labeling the entire code block as synchronized. However, for large critical areas, we had better divide the code into small critical areas and synchronize different critical areas respectively for the sake of execution efficiency, because we know that the influence of synchronized keyword is relatively large. If we synchronize the entire critical section directly, it is likely to affect the throughput of the critical section. Let’s use the following example to illustrate:

public class TwoSums { private int sum1 = 0; private int sum2 = 0; public void add(int val1, int val2){ synchronized(this){ this.sum1 += val1; this.sum2 += val2; }}}Copy the code

To avoid resource racing, we synchronized the two addition operations so that only one of them can be added at a time.

However, since both sum variables are independent of each other, we can separate the two variables into different synchronized blocks:

public class TwoSums { private int sum1 = 0; private int sum2 = 0; private Integer sum1Lock = new Integer(1); private Integer sum2Lock = new Integer(2); public void add(int val1, int val2){ synchronized(this.sum1Lock){ this.sum1 += val1; } synchronized(this.sum2Lock){ this.sum2 += val2; }}}Copy the code