当前位置:网站首页>[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 .
边栏推荐
- SwiftUI 布局 —— 对齐
- Added the ability of class @published for @cloudstorage
- 【七夕】七夕孤寡小青蛙究极版?七夕节最终章!
- Summarize the knowledge points of the ten JVM modules. If you don't believe it, you still don't understand it
- Career planning of Software Test Engineer
- How to effectively conduct the review meeting (Part 1)?
- Swiftui 4.0's new navigation system
- 围绕新市民金融聚焦差异化产品设计、智能技术提效及素养教育
- 多所“双一流”大学,保研预报名启动!
- 8、 Picker usage drop-down box selection effect
猜你喜欢

How to use the C language library function getchar ()

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

@DS('slave') 多数据源兼容事务问题解决方案
C # 7 methods to obtain the current path
![[ecmascript6] async and await](/img/3c/c7de42ad572dc95b188243c02dd228.png)
[ecmascript6] async and await

linux安装mysql

树莓派基础 | 总结记录树莓派学习过程中的一些操作

Hcip day 10

ScottPlot入门教程:获取和显示鼠标处的数值

2022 safety officer-a certificate operation certificate examination question bank simulated examination platform operation
随机推荐
多线程顺序运行有几种方法?
如何让照片中的人物笑起来?HMS Core视频编辑服务一键微笑功能,让人物笑容更自然
数字化转型安全问题频发,山石网科助力数字政府建设
Excel VBA password free view VBE encryption code
使用Weka与Excel进行简单的数据分析
Xcode编写SwiftUI代码时一个编译通过但导致预览(Preview)崩溃的小陷阱
Minitest -- applet automation testing framework
@DS('slave') 多数据源兼容事务问题解决方案
九、uni-popup用法 下拉框底部弹窗效果
Animation mechanism of swiftui
C # 7 methods to obtain the current path
String转为long 类型报错原因:要转为long必须是int、double、float型[通俗易懂]
Hcip day 12
The method of implementing simple student achievement management system with C language
十、时间戳
C # read INI file and key value pair operation
OKR与GRAD
Unittest executes runtestcase prompt <_ io. Textiowrapper name= '< stderr>' mode=W encoding=UTF-8 > solution
Summarize the knowledge points of the ten JVM modules. If you don't believe it, you still don't understand it
It's so hot that solar power can't take off? Hello, head