1.可见性
可见性(Visibility):是指一个线程对共享变量进行修改,另一个先立即得到修改后的最新值
1.1 可见性案例演示
一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另 一个线程并不会停止循环
public class VisibilityTest {// 多个线程都会访问的数据,我们称为线程的共享数据// 定义一个静态的 boolean 变量 run,初始值为 trueprivate static boolean run = true;public static void main(String[] args) throws InterruptedException {// 创建并启动线程 t1Thread t1 = new Thread(() -> {// 在 run 变量为 true 时循环输出消息while (run) {System.out.println(Thread.currentThread().getName() + "线程1 run = " + run);}// 循环结束后输出最终消息System.out.println(Thread.currentThread().getName() + "线程1 run = " + run);});t1.start(); // 启动线程 t1Thread.sleep(1000); // 主线程睡眠1秒钟// 创建并启动线程 t2Thread t2 = new Thread(() -> {run = false; // 将 run 变量设置为 falseSystem.out.println(Thread.currentThread().getName() + "时间到,线程2设置为false");});t2.start(); // 启动线程 t2}}public class VisibilityTest { // 多个线程都会访问的数据,我们称为线程的共享数据 // 定义一个静态的 boolean 变量 run,初始值为 true private static boolean run = true; public static void main(String[] args) throws InterruptedException { // 创建并启动线程 t1 Thread t1 = new Thread(() -> { // 在 run 变量为 true 时循环输出消息 while (run) { System.out.println(Thread.currentThread().getName() + "线程1 run = " + run); } // 循环结束后输出最终消息 System.out.println(Thread.currentThread().getName() + "线程1 run = " + run); }); t1.start(); // 启动线程 t1 Thread.sleep(1000); // 主线程睡眠1秒钟 // 创建并启动线程 t2 Thread t2 = new Thread(() -> { run = false; // 将 run 变量设置为 false System.out.println(Thread.currentThread().getName() + "时间到,线程2设置为false"); }); t2.start(); // 启动线程 t2 } }public class VisibilityTest { // 多个线程都会访问的数据,我们称为线程的共享数据 // 定义一个静态的 boolean 变量 run,初始值为 true private static boolean run = true; public static void main(String[] args) throws InterruptedException { // 创建并启动线程 t1 Thread t1 = new Thread(() -> { // 在 run 变量为 true 时循环输出消息 while (run) { System.out.println(Thread.currentThread().getName() + "线程1 run = " + run); } // 循环结束后输出最终消息 System.out.println(Thread.currentThread().getName() + "线程1 run = " + run); }); t1.start(); // 启动线程 t1 Thread.sleep(1000); // 主线程睡眠1秒钟 // 创建并启动线程 t2 Thread t2 = new Thread(() -> { run = false; // 将 run 变量设置为 false System.out.println(Thread.currentThread().getName() + "时间到,线程2设置为false"); }); t2.start(); // 启动线程 t2 } }
输出结果:
Thread-0线程1 run = trueThread-0线程1 run = true// .....结果省略Thread-1时间到,线程2设置为falseThread-0线程1 run = falseThread-0线程1 run = true Thread-0线程1 run = true // .....结果省略 Thread-1时间到,线程2设置为false Thread-0线程1 run = falseThread-0线程1 run = true Thread-0线程1 run = true // .....结果省略 Thread-1时间到,线程2设置为false Thread-0线程1 run = false
线程1开始运行时run=true
,所以会不断循环输出Thread-0线程1 run = true
,线程2运行后设置run=false
,线程1发现run=true后就停止输出
总结:并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改 后的最新值。
2.原子性
原子性(Atomicity):在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行
2.1 可见性案例演示
5个线程各执行1000次 i++
public class AtomicityTest {private static int number = 0; // 定义一个静态的整数变量 number,初始值为 0public static void main(String[] args) throws InterruptedException {Runnable increment = () -> {// 定义一个 Runnable 匿名类 increment,用于对 number 进行累加操作for (int i = 0; i < 1000; i++) {number++; // 对 number 进行累加操作}};ArrayList<Thread> ts = new ArrayList<>(); // 创建一个 ArrayList 用于存储线程对象for (int i = 0; i < 5; i++) {Thread t = new Thread(increment); // 创建一个新线程,传入 increment Runnable 实例t.start(); // 启动线程ts.add(t); // 将线程对象添加到 ArrayList 中}for (Thread t : ts) {t.join(); // 等待所有子线程执行完毕}System.out.println("number = " + number); // 输出最终的 number 值}}public class AtomicityTest { private static int number = 0; // 定义一个静态的整数变量 number,初始值为 0 public static void main(String[] args) throws InterruptedException { Runnable increment = () -> { // 定义一个 Runnable 匿名类 increment,用于对 number 进行累加操作 for (int i = 0; i < 1000; i++) { number++; // 对 number 进行累加操作 } }; ArrayList<Thread> ts = new ArrayList<>(); // 创建一个 ArrayList 用于存储线程对象 for (int i = 0; i < 5; i++) { Thread t = new Thread(increment); // 创建一个新线程,传入 increment Runnable 实例 t.start(); // 启动线程 ts.add(t); // 将线程对象添加到 ArrayList 中 } for (Thread t : ts) { t.join(); // 等待所有子线程执行完毕 } System.out.println("number = " + number); // 输出最终的 number 值 } }public class AtomicityTest { private static int number = 0; // 定义一个静态的整数变量 number,初始值为 0 public static void main(String[] args) throws InterruptedException { Runnable increment = () -> { // 定义一个 Runnable 匿名类 increment,用于对 number 进行累加操作 for (int i = 0; i < 1000; i++) { number++; // 对 number 进行累加操作 } }; ArrayList<Thread> ts = new ArrayList<>(); // 创建一个 ArrayList 用于存储线程对象 for (int i = 0; i < 5; i++) { Thread t = new Thread(increment); // 创建一个新线程,传入 increment Runnable 实例 t.start(); // 启动线程 ts.add(t); // 将线程对象添加到 ArrayList 中 } for (Thread t : ts) { t.join(); // 等待所有子线程执行完毕 } System.out.println("number = " + number); // 输出最终的 number 值 } }
思考:最终number结果可能是多少?
结果可能是5000,也可能是小于5000的结果都有可能
对于 number++ 而言(number 为静态变量),实际会产生如下的 JVM 字节码指令:
9: getstatic #12 // Field number:I12: iconst_113: iadd14: putstatic #12 // Field number:I9: getstatic #12 // Field number:I 12: iconst_1 13: iadd 14: putstatic #12 // Field number:I9: getstatic #12 // Field number:I 12: iconst_1 13: iadd 14: putstatic #12 // Field number:I
可见number++是由多条语句组成,以上多条指令在一个线程的情况下是不会出问题的,但是在多线程情况下就可能会出现问题。比如一个线程在执行13: iadd时,另一个线程又执行9: getstatic。会导致两次number++,实际上只加了1。
总结:并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。
3.有序性
有序性(Ordering):是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码时的顺序
3.1. 有序性可见性案例演示
有序性 使用jcstress是java并发压测工具
pom文件,添加依赖
<dependency><groupId>org.openjdk.jcstress</groupId><artifactId>jcstress-core</artifactId><version>0.5</version></dependency><dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>0.5</version> </dependency><dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>0.5</version> </dependency>
代码
@JCStressTest@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")@Statepublic class OrderingTest {int num = 0;boolean ready = false;// 线程1执行的代码@Actorpublic void actor1(I_Result r) {if (ready) {r.r1 = num + num;} else {r.r1 = 1;}}// 线程2执行的代码@Actorpublic void actor2(I_Result r) {num = 2;ready = true;}}@JCStressTest @Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok") @Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger") @State public class OrderingTest { int num = 0; boolean ready = false; // 线程1执行的代码 @Actor public void actor1(I_Result r) { if (ready) { r.r1 = num + num; } else { r.r1 = 1; } } // 线程2执行的代码 @Actor public void actor2(I_Result r) { num = 2; ready = true; } }@JCStressTest @Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok") @Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger") @State public class OrderingTest { int num = 0; boolean ready = false; // 线程1执行的代码 @Actor public void actor1(I_Result r) { if (ready) { r.r1 = num + num; } else { r.r1 = 1; } } // 线程2执行的代码 @Actor public void actor2(I_Result r) { num = 2; ready = true; } }
I_Result 是一个对象,有一个属性 r1 用来保存结果思考:在多线程情况下可能出现几种结果?
- 情况1:线程1先执行actor1,这时ready = false,所以进入else分支结果为1。
- 情况2:线程2执行到actor2,执行了num = 2;和ready = true,线程1执行,这回进入 if 分支,结果为 4。
- 情况3:线程2先执行actor2,只执行num = 2;但没来得及执行 ready = true,线程1执行,还是进入else分支,结果为1。
注意:还有第四种情况
种结果0
- 情况4:线程2java编译后结果
ready = true;num = 2;ready = true; num = 2;ready = true; num = 2;
线程2先执行actor2,执行了ready = true,但没来得及执行执行num = 2,线程1执行,还是进入if分支,结果为0
![并发编程中的三个问题有哪些 图片[1]-并发编程中的三个问题有哪些-不念博客](https://www.bunian.cn/wp-content/uploads/2024/03/640-17.png)
运行测试:
mvn clean installjava -jar target/jcstress.jarmvn clean install java -jar target/jcstress.jarmvn clean install java -jar target/jcstress.jar
部分jcstress测试结果,0结果出现1384次
![并发编程中的三个问题有哪些 图片[2]-并发编程中的三个问题有哪些-不念博客](https://www.bunian.cn/wp-content/uploads/2024/03/640-1-9.png)
完整pox.xml
<properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><javac.target>1.8</javac.target><uberjar.name>jcstress</uberjar.name></properties><dependencies><dependency><groupId>org.openjdk.jcstress</groupId><artifactId>jcstress-core</artifactId><version>0.5</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><compilerVersion>${javac.target}</compilerVersion><source>${javac.target}</source><target>${javac.target}</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>2.2</version><executions><execution><id>main</id><phase>package</phase><goals><goal>shade</goal></goals><configuration><finalName>${uberjar.name}</finalName><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>org.openjdk.jcstress.Main</mainClass></transformer><transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"><resource>META-INF/TestList</resource></transformer></transformers></configuration></execution></executions></plugin></plugins></build><properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <javac.target>1.8</javac.target> <uberjar.name>jcstress</uberjar.name> </properties> <dependencies> <dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>0.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerVersion>${javac.target}</compilerVersion> <source>${javac.target}</source> <target>${javac.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.2</version> <executions> <execution> <id>main</id> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>${uberjar.name}</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.openjdk.jcstress.Main</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/TestList</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build><properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <javac.target>1.8</javac.target> <uberjar.name>jcstress</uberjar.name> </properties> <dependencies> <dependency> <groupId>org.openjdk.jcstress</groupId> <artifactId>jcstress-core</artifactId> <version>0.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerVersion>${javac.target}</compilerVersion> <source>${javac.target}</source> <target>${javac.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.2</version> <executions> <execution> <id>main</id> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>${uberjar.name}</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.openjdk.jcstress.Main</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/TestList</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>