阅读 97

Java高并发编程学习系列(一)-多线程引发的问题

概要:本系列文章主要讲述在高并发编程中经常会遇到的问题,以及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();
		}
	}
复制代码

image.png

以上代码,我们尝试创建五个线程对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的底层原理。

文章分类
后端
文章标签