1.并发编程的三个问题
1.可见性问题
可见性概念
可见性(Visibility):是指一个线程对共享变量进行修改,另一个先立即得到修改后的最新值。
在实际应用中,我们需要保证可见性,但是如果不用特殊的方法保证的话,线程之间是不具有可见性的。往往会出现的情况就是,一个线程明明把常量改了,已经运行的线程里这个常量值还是使用了没有更改前的。
用个具体的案例来揭示这个问题:
package com.xxx.concurrent_problem;
/**
案例演示:
一个线程对共享变量的修改,另一个线程不能立即得到最新值
*/
public class Test01Visibility{
//多个线程都会访问的数据,我们成为线程的共享数据
private static boolean run = false;
public static void main(String[] args) throws InterruptedException{
//t1线程不断的来读取run共享变量的取值
Thread t1 = new Thread(() -> {
while(run){
}
});
t1.start();
Thread.sleep(1000);
//t2线程对该共享变量的取值进行修改
Thread t2 = new Thread(() -> {
run = false;
System.out.println("时间到,线层2设置为false");
});
t2.start();
//可以观测得到t2线程对run共享变量的修改,t1线程并不能够读取到更改了之后的值;
//这就出现了可见性问题
}
}
究其问题根本还是是在于线程运行时会在自己的工作内存区复制一份 共享内存区自己需要的变量到工作内存。而在最后再返回给共享内存,也就是说,在线程一工作的时候,无论共享内存里该变量如何变化,该线程工作内存里的该变量都不会变化,所以导致了不能保证可见性的问题。
2.原子性问题
什么是原子性?
原子性(Atomicity):在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。
具体指的就是 线程对共享内存里的变量进行操作的时候要保证这个操作不会被其他线程干扰,直到当前线程工作完毕。
原子性问题演示:
package com.xxx.demo01_concurrent_problem;
import java.util.ArrayList;
/**
案例演示:5个线程各执行1000次 i++;
*/
public class Test02Atomicity{
private static int number = 0;
public static void main(String[] args) throws InterruptedException{
//5个线程都执行1000次i++
Runnable increment = () -> {
for( int i = 0 ; i < 1000; i++){
number++;
}
};
//5个线程
ArrayList<Thread> ts = new ArrayList<>();
for(int i = 0; i < 5 ; i++){
Thread t = new Thread(increment);
t.start();
ts.add(t);
}
for(Thread t : ts){
t.join();
}
System.out.println("number = "+ number);
}
}
最终结果 :可能出现<5000的情况
因为线程里的increment
操作没有加锁,导致操作的中途,变量被其他线程操作,引起操作失败。
引起失败的详细解释:
扩展:java反汇编的命令: javap -p -v .\Test02Atomicity.class
这就是我们所谓的原子性问题!
3.有序性问题
有序性(Ordering):是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码时的顺序。
也就是说,实际运行可能与我们写的执行顺序不一致。
这一定会出现很多问题。
例如:
package com.xxx.concurrent_problem;
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.I_Result;
@JCStressTest
@OutCome(id = {"1" , "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
@OutCome(id = 0, expect = EXPECT.ACCEPTABLE_INTERESTING, desc = "danger")
@State
public class Test03Orderliness{
int num = 0;
boolean ready = false;
@Actor
public void actor1(I_Result r){
if(ready){
r.r1 = num + num;
}else{
r.r1 = 1;
}
}
//线程二 执行的代码;对两个变量进行相应的修改;
@Actor
public void actor2(I_Result r){
num = 2;
ready = true;
}
}
上面代码里 线程二的两条代码并没有很明确的逻辑关系,所以是可以互换的。但加入更换,必然会导致num的值错误。这就是常见的有序性问题,这是我们不愿意看到的。
扩展:压力测试 jcstress工具
官网:https://wiki.openjdk.java.net/display/CodeTools/jcstress
添加依赖:
<dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>${jcstress.version</version> </dependency>
使用方法:
通过jcstress来进行检测结果;
打开idea终端;即Terminal;
键入命令行:mvn clean install
完成之后会在target目录当中形成两个jar包(前提是安装了jcstress的依赖;) - jcstress.jar、Synchronized-1.0-SNAPSHOT.jar
此时运行jcstress.jar该jar包;
C:\Users\13666\IdeaProjects\XXX\Synchronized> java -jar target/jcstress.jar
运行之后,进行多轮的压力测试;