Through the previous 7 articles, you may feel that concurrent programming is very complicated. You have to consider whether the program result is correct, whether the program can be executed, and whether the server can hold it. I really don’t know where to start~
Don’t be afraid, although it looks like a lot, it’s actually a process of fighting monsters and upgrading. There are three levels: security, activity, and performance. Each level can be rewarded. If all three levels are passed , Also mastered this high-level skill.
Today, we first pass the first pass: security issues.
Security issues
so-called security issue refers to whether the program is executed in accordance with our expectations. To put it bluntly, it is correctness. believe that when you are working, you will definitely be asked: Does this method achieve thread safety? Is this class thread safe?
So what is thread safety? The essence of thread safety is correctness, and the program executes according to our expectations. Specifically, no matter in a single-threaded environment or in a multi-threaded environment, the final running result is the same and will not change with the environment.
If a program wants to achieve thread safety, it must avoid three problems: visibility, order, and atomicity. Regarding these issues, you can read the previous article: Java Concurrent Programming-Concurrency Root , which details the causes and consequences of these three issues.
After clarifying the ins and outs of the problem, we have to solve the problem. However, when it comes to concurrent programming, the great gods of all walks of life are excited, all kinds of professional terms, all kinds of interpretation of the source code, just don't mention how to write code.
But in fact, to achieve thread safety, it is not that difficult to write a multi-threaded application. You can take a look at this article, Java Concurrent Programming-Solving Concurrency , which is to get rid of your fear of difficulty, starting from actual work, solving problems one by one.
The cause of the problem has been figured out, and the solution has been established. Is it necessary to carefully check all the code and ensure 100% thread safety?
Of course not, the cost of implementing thread safety is very high, and it takes a lot of your time and energy. Moreover, concurrent programming is a high-level skill after all, which means that although this skill is very important, it is rarely used.
only needs to achieve thread safety when the shared data will change. Specifically, only when multiple threads read and write a data at the same time, you need to consider the visibility, order, and atomicity of the program.
In other words, adding, deleting, modifying, and investigating business is almost done, and you have to focus your time and energy on important business. For example, bank transfer and cash withdrawal services, e-commerce orders and inventory reduction services, etc.
Seeing this, you might think this way: I already know the root cause of the concurrency problem, and I have a solution, and I know to pay special attention to certain businesses, but I'm still confused and don't know where to start.
It doesn't matter, I will give you two more grabs.
In the context of data competition, concurrency must be considered
Data competition means that multiple threads simultaneously access and modify a shared data, which will cause concurrent BUG. There are two keywords here: multithreading, modifying a shared data, you see the following code.
class Account {
// 余额
private Integer balance = 1000;
// 充值
void charge(Integer amt) {
this.balance += amt;
}
}
The above is a recharge code, if the recharge is done one by one, this is no problem at all. Because the two conditions are not met, the balance-balance is a shared variable, but few recharges come in a day, let alone someone recharges at the same time.
But the company will have a big day. If tens of thousands of recharges come in at the same time, then the two conditions will be gathered, and the final balance will definitely be a mess. Let's analyze in detail, this code will have visibility and atomicity issues at the same time.
Let's talk about visibility first. Now is the era of multi-core CPUs, each core has its own CPU cache. If one recharge runs on CPU-1, another recharge runs on CPU-2. This is equivalent to reading the balance-balance at the same time, and at the same time modifying
balance
, but the two parties have no communication and have no idea what the other party did. In the end, balance
must have been wrong.
Let's look at the issue of atomicity again. Java is a high-level programming language, and a statement is often broken into multiple CPU instructions. For example, the eighth line of code this.balance -= amt;
is broken down into:
- Read the memory and load the balance to the CPU;
- CPU performs balance-amt operation;
- Write the final balance into memory;
This was originally a complete process, but the computer has a thread switching mechanism. Once a thread switching occurs, the result cannot be guaranteed.
Regarding the issue of visibility and atomicity, you can read the previous article: Java Concurrent Programming-Concurrency Root , which has a more detailed analysis.
To sum up, when multiple threads access and modify a shared data at the same time, it will cause data competition and concurrent BUG. The data competition must meet two conditions: multithreading and modifying a shared data. As long as these two conditions are in place, you have to take protective measures.
As for what protective measures to take, you can refer to this article: Java Concurrent Programming-Solving Concurrency .
Code with race conditions must be mutually exclusive
so-called race condition refers to the execution result of the program, which will change with the execution order of the threads. This sounds a bit awkward, let's take a look at a practical example.
In the withdrawal operation, there is a conditional judgment: the withdrawal amount cannot be greater than the account balance. However, if there are several withdrawals at the same time and no preventive measures are taken, there will be an over withdrawal problem.
class Account {
// 余额
private Integer balance = 150;
// 提现
void withdraw(Integer amt) {
if (balance >= amt) {
this.balance -= amt;
}
}
}
For example, account A has only 150 yuan, but thread one and thread two have to withdraw 100 yuan. Normally, only one transfer can be successful.
But if thread 1 and thread 2 execute at the same time to line 7 if (balance >= amt)
, they both find that the cash withdrawal amount is 100 yuan, which is less than the account balance of 150 yuan, so both withdrawals continue to be executed. Did you lose 50 yuan in vain?
Seeing this, I believe you can probably understand what race conditions are. Simply put, you have to pay special attention to such code:
if (状态变量 满足 执行条件) {
状态变量 = new 状态变量
}
Moreover, the race conditions are very special and cannot be simply classified. It is neither an atomicity problem, nor a visibility problem, nor a problem of ordering. It is purely because the program does not support concurrent access and must be processed one by one.
In this case, we can only adopt the mutual exclusion scheme, specifically: lock. You can review these two articles: Java Concurrent Programming-Solving Concurrency , Java Concurrent Programming-Correct Posture with Lock
Write at the end
Concurrent programming is a process of killing monsters and upgrading. There are 3 levels in it: security issues, activity issues, and performance issues.
The first hurdle is the security issue, which refers to whether the program is executed according to our expectations.
The first level is not difficult. We just need to pay attention to whether the program will have data competition and race conditions, and then take protective measures. You can review these articles: Java Concurrent Programming-Solving Concurrency , Java Concurrent Programming posture with lock 160c57096c84d0
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。