An article reads Java concurrency and thread security, and understands java concurrent threads

Source: Internet
Author: User

An article reads Java concurrency and thread security, and understands java concurrent threads

I. Preface

For a long time, I have been trying to analyze the essence of Java thread security. However, I had to put it on hold because I had some micro-points that I couldn't understand, all the points are connected in a series, and the ideas are clear and compiled into such an article.

Ii. Reading Guide

1. Why multithreading?

2. What are the essential problems of thread security description?

3. Java Memory Model (JMM) Data visibility, Command Re-sorting, and memory barrier

Iii. Reveal answers

1. Why multithreading?

Speaking of multithreading, we can easily draw an equal sign with high performance, but this is not the case. To give a simple example, from 1 to 100, using four threads to calculate is not necessarily faster than a thread. Thread creation and context switching are a huge overhead.

So what is the original intention of designing multithreading? Let's look at a practical example. A computer usually needs to interact with people. Assume that the computer has only one thread and the thread is waiting for user input, you can't do anything about the CPU. You can only wait, resulting in low CPU utilization. If multiple threads are designed, the CPU can be switched to other threads while waiting for resources to improve the CPU utilization.

Most modern processors contain multiple CPU cores. For large computing tasks, you can use multiple threads to split them into multiple small tasks for concurrent execution, improving the computing efficiency.

To sum up, there are only two points to improve CPU utilization and computing efficiency.

2. essence of thread security

Let's take an example:

public class Add {private int count = 0;public static void main(String[] args) {CountDownLatch countDownLatch = new CountDownLatch(4);Add add = new Add();add.doAdd(countDownLatch);try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(add.getCount());}public void doAdd(CountDownLatch countDownLatch) {for (int i = 0; i < 4; i++) {new Thread(new Runnable() {public void run() {for (int j = 0; j < 25; j++) {count++;}countDownLatch.countDown();}}).start();}}public int getCount() {return count;}}

The above is an example of auto-incrementing variables for 100 times. Only four threads are used, and each thread is auto-incrementing for 25 times. The execution is completed using four threads, such as CountDownLatch, and the final result is printed. In fact, we hope that the program will produce 100 results, but the printed results are not always 100.

This raises the thread security description. Let's first describe thread security in plain words:

Thread security is to let the Program program run the desired results, or let the program be executed as we can see in a sentence.

To explain the summary, we first created an add object and called the doAdd method of the object. Originally, we wanted to auto-increment every thread for 25 times in an orderly manner, finally, get the correct result. If the program is added to run as we set in advance, this object is thread-safe.

Next let's take a look at Brian Goetz's description of thread security: when multiple threads access an object, if you don't have to consider the scheduling and alternation of these threads in the runtime environment, no extra synchronization is required, or the caller can perform any other coordination operations to call this object to obtain the correct results. Therefore, this object is thread-safe.

Next we will analyze why this Code cannot always get the correct results.

3. Java Memory Model (JMM) Data visibility, Command Re-sorting, and memory barrier

Starting from the computer's Hardware efficiency, the CPU computing speed is several orders of magnitude faster than the memory. To balance the conflicts between the CPU and memory, the high-speed cache introduced, each CPU has a high-speed cache, even multi-level cache L1, L2, and L3, the interaction between the cache and the memory requires a cache consistency protocol, which is not described in detail here. The interaction between the final processor, high-speed cache, and main memory is as follows:

The Java Memory Model (JMM) also defines the relationship between threads, working Memory, and main Memory, which is very similar to the definition of hardware.

Here, we will mention the memory partition during Java Virtual Machine runtime.

Method Area: storage class information, constants, static variables, etc., shared by various threads

Virtual Machine Stack: Stack frames are created for execution of each method to store local variables, operand stacks, and dynamic links. The virtual machine stack mainly stores the information, and the thread is private.

Local method Stack: Native METHOD services used by virtual machines, such as c Programs, private threads

Program counter: records the row where the program is running, which is equivalent to the row number counter of the current thread bytecode. The thread is private

Heap: new instance objects are stored in this region, which is the main battlefield of GC and shared by threads.

Therefore, for the main memory defined by JMM, most of the time it can correspond to the areas shared by the heap memory, method areas, and other threads. Here is only a conceptual correspondence, in fact, some program counters, virtual machine stacks, and so on are also placed in the main memory, depending on the virtual machine design.

Now that we understand the JMM memory model, let's analyze why the above program didn't get the correct result. Please note that threads A and B read the initial count values of the primary memory at the same time and store them in their respective working memory. At the same time, they perform the auto-increment operation and write them back to the primary memory, and finally get the wrong result.

Let's further analyze the underlying causes of this error:

(1) visibility: the newest value of the working memory does not know when it will be written back to the primary memory.

(2) In order, threads Must Be orderly access sharing variables. We use the concept of "Horizon" to describe this process, from the perspective of line B, after reading the operation of thread A, he writes the value back to the memory and immediately reads the latest value for calculation. Thread A should also see that thread B reads data immediately after computation, and performs computation, so that the correct result is obtained.

Next, let's analyze in detail why we need to limit the visibility and orderliness.

Adding the volatile keyword to count ensures visibility.

private volatile int count = 0;

The volatile keyword will add the lock prefix and lock prefix to the final compiled command to do three things.

(1) Prevent Command Re-sorting (the analysis on this issue is not important and will be detailed later)

(2) Locking the bus or using the locking cache to ensure the atomicity of execution. Early processing may use the locking bus mode, so that other processors cannot access the memory through the bus, causing high overhead, the current processor uses the lock cache method to solve the problem with cache consistency.

(3) write all data in the buffer zone back to the primary memory, and ensure that the variable cached by other processors is invalid.

Since the visibility is ensured and the volatile keyword is added, why can't we get the correct result? The reason is that count ++ is not an atomic operation. count ++ is equivalent to the following steps:

(1) read the count value from the main memory and assign it to the thread copy variable:

Temp = count

(2) thread copy variable plus 1

Temp = temp + 1

(3) write the thread copy variable back to the main memory

Count = temp

Even if the bus is strictly locked, only one processor can access the count variable at the same time. However, when performing step (2), other CPUs can access the count variable, at this time, the latest computation results have not been flushed back to the primary memory, resulting in incorrect results. Therefore, order must be ensured.

The essence of order is to ensure that only one CPU can execute the critical code at the same time. At this time, the practice is usually locking, the essence of the lock is divided into two types: Pessimistic lock and optimistic lock. For example, a typical pessimistic lock synchronized or JUC package contains a typical optimistic lock ReentrantLock.

To ensure thread security, you must ensure the visibility of shared variables and the sequence of code access in the critical section.

Source: https://my.oschina.net/u/1778239/blog/1610185

References: http://www.roncoo.com/course/view/b6f89747a8284f44838b2c4da6c8677b

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.