1.案例
1.1.困惑的i++操作
简述:
1.在我们的日常开发中,经常会写:i++这样的操作
2.问题:那么它到底是不是线程安全的呢?
3.关键点:问题的关键在于i++是不是原子性操作。即i++对于操作系统,或者说对于jvm执行子系统,是一条指令,还是多条指令?
1.1.1.案例代码
package com.anan.thread.threadsafe;
/**
* 让人困惑的i++操作
*/
public class ThreadSafeIAddOper {
// 定义自增操作变量:i
public static int i_add = 0;
// 在方法中,进行i_add的自增操作
public static void addI(){
i_add++;
}
public static void main(String[] args) {
// 创建20个线程,并行执行i_add自增操作
Runnable r1 = new MyRunnable();
// for循环,创建20个线程
for (int i = 0; i < 20; i++) {
new Thread(r1).start();
}
// 等待20个子线程执行结束后,主线程main输出i_add的值
while(Thread.activeCount() > 2){
;
}
System.out.println("i_add变量最终值:" +i_add);
}
/**
* 实现Runnable接口,创建线程
*/
static class MyRunnable implements Runnable{
public void run() {
// for循环,执行i_add自增操作:10000次
for (int i = 0; i < 10000; i++) {
addI();
}
}
}
}
1.1.2.执行结果
1.1.3.ThreadSafeAddOper字节码文件内容
简述:
1.彩蛋:通过javap工具,查看字节码文件结构
2.说明i++操作,对于jvm执行子系统,不是原子性(是由多条指令组成)
3.以下是类:ThreadSafeIAddOper,对应的class文件内容
D:\03other\02study\coding\mypro\thread-pro\target\classes>javap -v com.anan.thread.threadsafe.ThreadSafeIAddOper
Classfile /D:/03other/02study/coding/mypro/thread-pro/target/classes/com/anan/thread/threadsafe/ThreadSafeIAddOper.class
Last modified 2020-2-15; size 1259 bytes
MD5 checksum 6b289d7c5da1749f03e41da116a3b9a6
Compiled from "ThreadSafeIAddOper.java"
public class com.anan.thread.threadsafe.ThreadSafeIAddOper
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
......内容省略......
public static void addI();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #10 // Field i_add:I
3: iconst_1
4: iadd
5: putstatic #10 // Field i_add:I
8: return
LineNumberTable:
line 13: 0
line 14: 8
LocalVariableTable:
Start Length Slot Name Signature
public static void main(java.lang.String[]);
.......内容省略......
D:\03other\02study\coding\mypro\thread-pro\target\classes>
1.1.4.i++对应的字节码指令说明
简述:
1.通过截图,可以看到一个i++操作,在字节码层面,对应了四条jvm字节码指令:
getstatic、iconst_1、iadd、putstatic
2.说明对于jvm来说,i++不是原子性操作
1.2.线程安全基本手段:锁
简述:
1.改造3.1案例代码,通过加锁实现:多条指令操作的原子性。从而实现线程安全。
2.给addI方法,增加synchronized同步锁
执行结果:
1.3.关键字volatile错误使用案例
简述:
改造3.1.案例代码,通过volatile关键字修饰:
1.说明volatile关键字,只能保障线程的可见性(即一个线程修改了volatile关键字修改的变量后,会立即刷新到主内存,让其它线程可见)。
2.但volatile关键字,不能保障原子性,对于i++操作,它还是不能保障线程安全
3.关于volatile关键字的正确使用方式,请看讨论分享中内容说明。
执行结果:
关于作者
王硕,网名信平,十多年软件开发经验,业余架构师,精通Java/Python/Go等,喜欢研究技术,著有《PyQt 5 快速开发与实战》《Python 3.* 全栈开发》,多个业余开源项目托管在GitHub上,欢迎微博交流。