当前位置:网站首页>[thread safety] what risks may multithreading bring?
[thread safety] what risks may multithreading bring?
2022-07-28 14:43:00 【Caixinzhi】
List of articles
- 1. The goal is
- 2. What is thread safety
- 3. Thread safety problems and the causes of thread insecurity
- 3.1 Thread safety problem 1 : Random scheduling of operating system
- 3.2 Thread safety problem 2 : Multiple threads modify the same variable
- 3.3 Thread safety problem 3 : The modification operation is not atomic
- 3.4 Thread safety problem 4 : Memory visibility
- 3.5 Thread safety problem five : The instructions are reordered
- 4. The solution to the above thread safety problem
1. The goal is
The ultimate goal of this article is to master the situation of multithreading , There will be security problems , And why such problems occur , Finally, the corresponding solution is introduced .
2. What is thread safety
Thread safety is the result of executing code in the case of multithreading, if and as expected ( The result of execution in the case of single thread ) The same as , So it's thread safety . otherwise , It's just that the thread is not safe , At this time, thread safety problems are likely to occur .
3. Thread safety problems and the causes of thread insecurity
Before exploring thread safety , Here is an example of thread insecurity and the running results to lead to the following :
// Create two threads , Let these two threads execute a variable concurrently , Self increase respectively 5w Time , Finally, it is estimated that the total self increase 10w Time
class Counter{
// Variables that hold counts
public int count;
public void increase(){
count++;
}
}
public class Main {
public static void main(String[] args) {
Counter counter=new Counter();
Thread thread1=new Thread(() -> {
for(int i=0;i<50000;i++){
counter.increase();
}
});
Thread thread2=new Thread(() -> {
for(int i=0;i<50000;i++){
counter.increase();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count="+counter.count);
}
}
Running results :
Here we will find : After we run this code , The results are less than 10 Ten thousand , And the results of each run are different ( But the probability is 5 and 10 Between ten thousand , The specific reasons will be explained later ), It's completely different from what we expected , What we want is to use multithreading to improve running efficiency and achieve the target effect , And the results of the operation are similar to 10 Obviously, there is a big difference . Why is that ?
3.1 Thread safety problem 1 : Random scheduling of operating system
Random scheduling of operating system ( Or preemptive execution ) Is the most fundamental cause of thread insecurity . Because of the thread thread1 And thread thread2 It's concurrent , So when these two threads are executed concurrently in the operating system , It may happen that two threads read the same data in memory at the same time , But finally, after the two threads are executed , What is stored in memory will only be the value stored in memory ( This is just one of the possible situations , It may also occur before a thread stores the calculated data in memory , Another thread is already reading data in memory …), In short, the random scheduling of this operating system makes the order of executing threads random , Never guess what the next instruction in the operating system will execute , Very complicated … This is one of the reasons why threads are unsafe , It is also the most fundamental reason .
3.2 Thread safety problem 2 : Multiple threads modify the same variable
Just like the above , Two threads (CPU) Come to the same piece ( Memory ) Variable to modify ( In the example, add ) When , Thread safety problems are likely to occur . Be careful : There are three key points — 1. Multiple threads -> 2. For the same variable -> 3. And it must be modified ( Pure read operation will not cause thread safety problems ). In the end, this is also the random scheduling of the operating system ( uncertainty ) Caused by , Because if only a thread modifies variables ( This is equivalent to ordinary code written before , There is no need to consider thread safety ), Or multiple threads read the same variable , Or multiple threads to modify multiple variables ( This is equivalent to each thread performing its own duties , Disguised single thread ), Will not cause thread safety problems , When multiple threads modify the same variable , The order in which the operating system reads and writes memory is unknown , This is one of the reasons why threads are unsafe .
3.3 Thread safety problem 3 : The modification operation is not atomic
In the above code , Counter Class increase() Every time the method is executed ++ operation , The bottom layer of the operating system will carry out three-step operations ( Three instructions ): 1. Load data in memory CPU(LOAD); 2. stay CPU Perform addition operation in (ADD); 3. Store the calculated results in memory (SAVE). We regard these three operations as a modification operation . Atomicity comes before MySQL Things in ( The core characteristic of things ) Just introduced , In short , Atomicity is to regard some operations as an inseparable whole .
that , Why is it that if the modification operation is not atomic, it may lead to thread insecurity ?
In fact, this is also caused by the random scheduling of the operating system . If the above three steps are separated , If it's just a single thread , It won't make any difference , But there are two threads in the sample code , In terms of time sequence , These three steps ( Three instructions ) It is very likely to be executed in a staggered way , This will cause the modified result to be wrong . This is one of the reasons why threads are unsafe .
3.4 Thread safety problem 4 : Memory visibility
The memory visibility problem is actually when the operating system optimizes the code , Thread safety problems caused by . If there are some operations in the thread that have been doing a certain work repeatedly , At this time, the operating system may optimize it , Make memory invisible , Instead, read the contents of the register directly , This may omit some repeated computer instructions , Keep valid instructions . The most classic is in the case of single thread , We're executing ++ When , Need experience LOAD , ADD , SAVE Three instructions can be completed , But if you are executing a loop ++ During operation , The operating system will optimize the three instructions that have been looping into LOAD , ADD , ADD , ADD … , ADD , SAVE . in other words , The original three instruction loop is optimized to run only once LOAD and SAVE , And in the ADD A cycle is carried out on , This can greatly reduce the time of repeatedly reading and writing memory , It improves the efficiency of calculation . Of course , In such a single threaded case , There will be no safety problems , The results are also correct , But in the case of multithreading , There is likely to be a problem , Here is a classic example ( The following code ):
public class Main {
public static int flag=0;
public static void main(String[] args) {
Thread thread1=new Thread(() -> {
while(flag==0){
try {
;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(" The loop ends ");
});
Thread thread2=new Thread(() -> {
Scanner scanner=new Scanner(System.in);
System.out.println(" Please enter an integer ");
flag=scanner.nextInt();
});
thread1.start();
thread2.start();
}
}
Because in the thread thread1 Repeat in flag The value of , And the judgment is true ( Into the circulatory body ), Now , When optimizing the operating system , This step may be omitted , This leads to if in the thread thread2 Yes flag Changing the value will not make the thread thread1 Out of the loop . This is one of the reasons why threads are unsafe .
3.5 Thread safety problem five : The instructions are reordered
Instruction reordering is also a thread safety problem in the process of operating system optimization . Instruction reordering is actually an optimal solution of the logical order of instruction execution that the operating system helps us find , So as to improve the efficiency of code execution . Like , There are several instructions that can complete a thing , But when you change the order of these instructions , You may get a better solution , Now , The operating system is likely to optimize these instructions directly , To improve code execution efficiency . Of course , If this is in the case of a single thread, it must be all right , Because no matter how the order of instructions is adjusted , The final result of the implementation is still like that , It's just an optimization . But in the case of multithreading , Adjustment of instruction sequence , Is the difference that will cause the final execution result , This is one of the reasons why threads are unsafe .
4. The solution to the above thread safety problem
4.1 in the light of Question 1
For problem 1, the random scheduling of operating system , We have no way to solve it , This random scheduling of the operating system in multithreading is very annoying , All we can do is avoid it when necessary , It is impossible and impossible to modify the random scheduling feature of the operating system .
4.2 in the light of Question two
For problem 2, multiple threads modify the same variable , In fact, we can adjust the structure of the code directly , Don't let multiple threads modify the same variable .
But someone here asked : If I have to modify the same variable through multithreading , Is there any solution ? The answer you want is below , Please read on .
4.3 in the light of Question 3
For problem 3, the modification operation is not atomic , Take the first code above , Here's our solution : take LOAD ADD SAVE These three instructions perform lock operation ( That is to pack these three instructions together , This is an inseparable whole , There will be no thread safety problems ).
stay Java There are many kinds of locking operations in , Here is a common locking method : Use synchronized keyword .
4.3.1 Detailed explanation synchronized keyword
there synchronized Keywords are " Sync " It means , Of course , there " Sync " No IO The scene or the upper and lower levels call the scene " Sync " and " asynchronous ". here " Sync " It means " Mutually exclusive ", in other words , If you add synchronized keyword , Then it is equivalent to locking this method , When calling this method in a thread , Because of the lock , So this is when other threads want to call this method again , You need to block and wait , Until the method call ends and the lock is unlocked , Can be called by other threads , In this way " Mutually exclusive " The effect of . Of course , " Sync " There are other meanings , Here is a supplementary point : stay IO Under the scenario, or the upper and lower levels call under the scenario , " Sync " It means that the caller is responsible for the operation of obtaining the call result ; " asynchronous " It means that the caller is not responsible for obtaining the call result , Instead, the callee actively pushes the calculated result to the caller .
Use synchronized There are two cases of keyword locking :
1. Lock the object . When locking objects , There are two ways of writing :
(1) Direct modification of common methods , The sample code is as follows :
public class SynchronizedDemo {
synchronized public void methond() {
...
}
}
(2) Use specified this Decorated code block , The sample code is as follows :
public class SynchronizedDemo {
public void method() {
synchronized (this) {
...
}
}
}
Be careful : The above two are just written differently , The end result is the same , So these two ways of writing are equivalent .
2. Lock class objects . When locking class objects , There are also two ways of writing :
(1) Direct modification of static methods , The sample code is as follows :
public class SynchronizedDemo {
synchronized public static void method() {
...
}
}
(2) Use the specified class .class Decorated code block , Examples are as follows :
public class SynchronizedDemo {
public void method() {
synchronized (SynchronizedDemo.class) {
...
}
}
}
Be careful : (1) The above two are just written differently , The end result is the same , So these two ways of writing are equivalent . (2) The second method specifies the class .class It is not necessary to use this class ( The class corresponding to this method ), It can also be other classes .
In the use of synchronized When locking keywords , We just need to recognize : Two threads must be the same lock , Will happen blocking and waiting , Otherwise, there will be no blocking waiting , Because they don't point to the same lock ( It can be understood that these two threads are not atomic ). that , How can we judge whether two threads point to the same lock ?
Situation 1 : Lock the object . If the locking method in the same object is used in two threads , Then the thread executing later will block and wait . But if two threads use different object locking methods , Then there will be no blocking and waiting . for instance :
class A{
synchronized public void m1(String a){
System.out.println(a+" Start m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m1");
}
synchronized public void m2(String a){
System.out.println(a+" Start m2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m2");
}
}
public class Main {
public static void main(String[] args) {
A a1=new A();
A a2=new A();
Thread thread1=new Thread(() -> {
a1.m1(" Threads 1");
});
Thread thread2=new Thread(() -> {
a2.m1(" Threads 2");
});
thread1.start();
thread2.start();
}
}
Running results :
Although there are synchronized modification , But because of the two threads used separately a1 and a2 It's two different objects , So they will not block and wait .
class A{
synchronized public void m1(String a){
System.out.println(a+" Start m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m1");
}
synchronized public void m2(String a){
System.out.println(a+" Start m2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m2");
}
}
public class Main {
public static void main(String[] args) {
A a1=new A();
A a2=new A();
Thread thread1=new Thread(() -> {
a1.m1(" Threads 1");
});
Thread thread2=new Thread(() -> {
a1.m2(" Threads 2");
});
thread1.start();
thread2.start();
}
}
Running results :
Because both threads use the method of locking the same object , So there will be blocking and waiting , Operations like this are thread safe .
Situation two : Lock class objects . If the lock of the method called in two threads points to the same class , So even though they don't use the same object , There will also be blocking waiting ( That's thread safety ). So you could say , The key point of locking class objects is to see whether the lock of calling methods between different threads points to the same class .
class A{
synchronized public static void m1(String a){
System.out.println(a+" Start m1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m1");
}
synchronized public static void m2(String a){
System.out.println(a+" Start m2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end m2");
}
}
public class TestDemo4 {
public static void main(String[] args) {
Thread thread1=new Thread(() -> {
A.m1(" Threads 1");
});
Thread thread2=new Thread(() -> {
A.m2(" Threads 2");
});
thread1.start();
thread2.start();
}
}
Running results :
Of course , Locking class objects can also be for different classes , Blocking and waiting can also occur between two threads . Another example :
class B{
public void m(String a){
synchronized (B.class){
System.out.println(a+" Start ");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end ");
}
}
}
class C{
public void m(String a){
synchronized (B.class){
System.out.println(a+" Start ");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a+" end ");
}
}
}
public class TestDemo5 {
public static void main(String[] args) {
B b=new B();
C c=new C();
Thread thread1=new Thread(() -> {
b.m(" Threads 1");
});
Thread thread2=new Thread(() -> {
c.m(" Threads 2");
});
thread1.start();
thread2.start();
}
}
Running results :
4.3.2 Use synchronized Keyword in solving the problem of problem three
By facing up to synchronized After knowing the keywords , Solve problem 3. If the modification operation is not atomic, it will become very simple , Directly in execution ++ The method of operation plus synchronized Key words can be used . The specific code is as follows :
public class TestDemo {
static class Counter{
public int count;
synchronized public void crease(){
count++;
}
}
public static void main(String[] args) {
Counter counter=new Counter();
Thread thread1=new Thread(() -> {
for(int i=0;i<10000;i++){
counter.crease();
}
});
Thread thread2=new Thread(() -> {
for(int i=0;i<10000;i++){
counter.crease();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count="+counter.count);
}
}
4.4 in the light of Question 4 + Question five
For problem 4 memory visibility and problem 5 instruction reordering , In fact, they are all problems caused by multithreading after the operating system optimizes the code , Just like the code in question 4 above . So face this problem , How can we solve this problem ? When multithreading , We can use volatile Keyword to prevent ( prohibit ) The operating system optimizes the code ( That is to make the memory from invisible to visible and prevent the reordering of instructions ), This keyword is actually adding a special binary instruction to the added variable — “ Memory protection ”. So the modified code can be :
public class Main {
volatile public static int flag=0;
public static void main(String[] args) {
Thread thread1=new Thread(() -> {
while(flag==0){
try {
;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(" The loop ends ");
});
Thread thread2=new Thread(() -> {
Scanner scanner=new Scanner(System.in);
System.out.println(" Please enter an integer ");
flag=scanner.nextInt();
});
thread1.start();
thread2.start();
}
}
add volatile after , There will be no optimization operation , The result of the operation is correct . It's a way , There is another way to avoid volatile Keywords will not be optimized , That is in this code thread thread1 Add sleep Method to block the code , At this time, the code cycle speed will slow down a little , The operation of reading and writing memory will not become so frequent , It will not trigger code optimization ( But here's the suggestion : Try to add volatile keyword , In order to avoid unnecessary impact of some optimizations on code logic ). The specific code is as follows :
public class Main {
public static int flag=0;
public static void main(String[] args) {
Thread thread1=new Thread(() -> {
while(flag==0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(" The loop ends ");
});
Thread thread2=new Thread(() -> {
Scanner scanner=new Scanner(System.in);
System.out.println(" Please enter an integer ");
flag=scanner.nextInt();
});
thread1.start();
thread2.start();
}
}
volatile There are two main functions of keywords :
(1) Ensure memory visibility : Implement based on barrier instructions , That is, when a thread modifies a shared variable , Another thread can read the modified value .
(2) To ensure order : Disable instruction reordering . Compile time JVM The compiler follows the constraints of the memory barrier , When running, the order of instructions is organized by barrier instructions .
One more thing to note : volatile Atomicity cannot be guaranteed .
Speaking of optimization , Here is a brief talk about the above optimization process , stay Java It is also called "JMM(Java Memory Model)".

The reason for the problem after optimization is : After thread optimization , Mainly in the operation of working memory , Failed to read the main memory in time , Thus leading to the phenomenon of misjudgment . among , Working memory here refers to CPU The register of ( May include CPU cache ); The main memory here is what the computer calls the real memory . therefore , We can also simplify the above paragraph into : After thread optimization , Mainly in operation CPU, Failed to read memory in time , Thus leading to the phenomenon of misjudgment .
5. summary
front MySQL The things mentioned in this article are very similar to multithreading , In fact, from a certain phenomenon , Things can be a simplified version of multithreading , They are all some problems that will occur during the execution of concurrency , And after solving these problems , Will make the code accurate ( Or isolation ) Improve , But it will sacrifice some operating efficiency .
But then again , To make a long story short , Multithreading does bring some risks , For this, we should be more bold and careful when writing code , ctively , Reduce the problems caused by multithreading bug The situation of .
边栏推荐
- Nport serial server configuration website (whether the serial server is from network port to serial port)
- Why is it reverse to convert from other formats to BMP
- [ecmascript6] modularization
- 九、uni-popup用法 下拉框底部弹窗效果
- Floating point data type in C language (did you learn to waste it)
- [Tanabata] Tanabata lonely little frog research edition? The final chapter of Tanabata Festival!
- Summarize the knowledge points of the ten JVM modules. If you don't believe it, you still don't understand it
- Redis-持久化
- Installing MySQL on Linux
- 多所“双一流”大学,保研预报名启动!
猜你喜欢

面试官:ThreadLocal使用场景有哪些?内存泄露问题如何避免?

【七夕】七夕孤寡小青蛙究极版?七夕节最终章!

Copy excel row to specified row

天气这么热太阳能发电不得起飞喽啊?喽啊个头……

BGP experiment

Mobile phone scrolling screenshot software recommendation

爆肝整理JVM十大模块知识点总结,不信你还不懂

How to reduce the resolution of only 3D camera but not UI camera
C # 7 methods to obtain the current path

2022 safety officer-a certificate operation certificate examination question bank simulated examination platform operation
随机推荐
2022 melting welding and thermal cutting examination questions and online simulation examination
Another way of understanding the essence of Hamming code
OKR与GRAD
8、 Picker usage drop-down box selection effect
Raspberry pie foundation | summarize and record some operations in the learning process of raspberry pie
C# 获取当前路径7种方法
Interviewer: what are the usage scenarios of ThreadLocal? How to avoid memory leakage?
Force deduction solution summary 1331 array sequence number conversion
Store and guarantee rancher data based on Minio objects
JS instantiation method
C语言中浮点数据类型(你学废了吗)
[ecmascript6] iterator and generator
HCIP第十一天
Recommended super easy-to-use mobile screen recording software
Xcode编写SwiftUI代码时一个编译通过但导致预览(Preview)崩溃的小陷阱
Langjing Technology (Trax China) "robot +ai" opens the era of Chinese retail meta universe
Bulk Rename Utility
SwiftUI 布局 —— 尺寸( 下 )
分集技术简略
[ecmascript6] proxy and reflection