概要:本系列文章主要讲述在高并发编程中经常会遇到的问题,以及java一些锁的底层实现原理和使用场景,本着学习和交流的态度,文章中若有不合理的地方,欢迎一起探讨。
1.由线程安全问题引发的思考
所谓的线程安全问题,就是程序在并发执行过程中,当多个线程同时共享,同一个全局变量或者静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。当程序在做只读操作时不会发生数据冲突问题。我们从三个问题去引出并发编程中的线程安全问题。
第一个问题:什么是可见性
从定义上来讲,可见性就是指一个线程对共享变量进行修改,另一个立即得到修改后的最新值。示例代码:
private static boolean flag= true;
public static void main(String []args) throws InterruptedException{
new Thread(()-> {
while(flag) {
}
}).start();
Thread.sleep(2000);
//创建新的线程去修改变量
new Thread(()->{
flag =false;
System.out.println("线程2修改了flag值为false");
}).start();
}
上面的代码,如果没有可见性问题,则线程1会立刻得到线程2修改后的结果,程序立刻退出,但是结果是程序并不会立刻退出。
第二个问题:什么是原子性
原子性:在一次或多次操作中,要么所有的操作都执行并且不会受其他因素的干扰而中断,要么所有的操作都不执行。
public static int count=0;
@Override
public void run() {
for(int i=0;i<1000;i++){
count ++;
}
System.out.println(count);
}
public static void main(String []args){
TestAtomicity testAtomicity = new TestAtomicity();
for(int k=0;k<5;k++){
Thread thread = new Thread(testAtomicity);
thread.start();
}
}
以上代码,我们尝试创建五个线程对count变量进行加一操作,开始运行代码,count的最终结果应该是5000,但实际情况并非如此,这就是由于并发环境下产生了资源竞争的问题。
第三个问题:什么是有序性
有序性:是指程序中代码的执行顺序,Java代码被编译是会对代码进行优化,导致执行顺序和编写的代码的顺序不一定是代码顺序。使用Jcstress压测工具模拟高并发场景。
首先在项目中引入JCStress工具包:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jcstress</groupId>
<artifactId>jcstress-core</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>org.openjdk.jcstress</groupId>
<artifactId>jcstress-samples</artifactId>
<version>0.5</version>
</dependency>
示例代码:
@JCStressTest
@Outcome(id= {"-1","5"},expect = Expect.ACCEPTABLE,desc = "good result")
@Outcome(id="0",expect = Expect.ACCEPTABLE_INTERESTING,desc = "bad result")
@State
public class TestOrdering {
int count;
boolean flag;
@Actor
public void actor1(I_Result r) {
if (flag) {
r.r1=count;
}else {
r.r1=-1;
}
}
@Actor
public void actor2(I_Result r) {
this.count=5;
flag =true;
}
}
在示例代码中,I_Result为JCStress包中的类,其会返回一个默认值0,此样例会在高并发下调用actor1和actor2方法各一次,按照正常逻辑,x最后的值要么是-1要么是5,如果actor2方法内的2行代码发生了指令重排序,就会导致x的值可能为0。
执行结果:
2 matching test results.
[OK] synchorizedLearn.TestOrdering
(JVM args: [-XX:+HeapDumpOnOutOfMemoryError, -Xms20m, -Xmx20m, -Dfile.encoding=UTF-8, -XX:-TieredCompilation])
Observed state Occurrences Expectation Interpretation
-1 110,851,095 ACCEPTABLE good result
0 13,868 ACCEPTABLE_INTERESTING bad result
5 70,844,788 ACCEPTABLE good result
[OK] synchorizedLearn.TestOrdering
(JVM args: [-XX:+HeapDumpOnOutOfMemoryError, -Xms20m, -Xmx20m, -Dfile.encoding=UTF-8])
Observed state Occurrences Expectation Interpretation
-1 84,874,341 ACCEPTABLE good result
0 13,883 ACCEPTABLE_INTERESTING bad result
5 74,437,997 ACCEPTABLE good result
从结果可以看出,代码在多线程环境下发生了指令重排。
本章小结
在多线程环境下,线程安全问题主要包含:原子性、可见性、有序性。下一章会介绍java内存模型(JMM),并从JAVA数据结构角度探讨synchronized的底层原理。