#回顾
1.多线程编程中,比较为难,又需要重点关注的一个话题,就是线程安全
2.需要从理论、和实战多个角度去看
3.本着一篇文章,信息量不要太多的原则
4.本篇文章仅相对全面的梳理线程安全的基础
5.更多内容,结合JUC的内容,推荐了解的内容有:
线程池、锁、CAS、ThreadLocal
并发集合、并发流程控制、AQS
#考考你
1.你知道多线程的理论基础有哪些吗?
2.你知道线程的实现方式有哪些吗?
3.你知道多线程安全的三要素吗?
4.你知道java的内存模型JMM吗?
5.你知道让线程安全的常规手段吗?
6.你知道java中的volatile关键字吗?
案例
困惑的i++操作
简述:
1.在我们的日常开发中,经常会写:i++这样的操作
2.问题:那么它到底是不是线程安全的呢?
3.关键点:问题的关键在于i++是不是原子性操作。即i++对于操作系统,或者说对于jvm执行子系统,是一条指令,还是多条指令?
案例代码
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();
}
}
}
}
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>
i++对应的字节码指令说明
简述:
1.通过截图,可以看到一个i++操作,在字节码层面,对应了四条jvm字节码指令:
getstatic、iconst_1、iadd、putstatic
2.说明对于jvm来说,i++不是原子性操作
线程安全基本手段:锁
简述:
1.改造案例代码,通过加锁实现:多条指令操作的原子性。从而实现线程安全。
2.给addI方法,增加synchronized同步锁
关键字volatile错误使用案例
简述:
改造案例代码,通过volatile关键字修饰:
1.说明volatile关键字,只能保障线程的可见性(即一个线程修改了volatile关键字修改的变量后,会立即刷新到主内存,让其它线程可见)。
2.但volatile关键字,不能保障原子性,对于i++操作,它还是不能保障线程安全
3.关于volatile关键字的正确使用方式,请看讨论分享中内容说明。
总结
#考考你答案
1.你知道多线程的理论基础有哪些吗?
1.1.进程与线程的区别
1.2.线程实现方式
1.3.线程安全三要素
1.4.java内存模型JMM
1.5.锁
2.你知道进程与线程的区别吗?
2.1.进程是操作系统【分配资源】的最小单位
2.2.线程是操作系统【调度】的最小单位
3.你知道线程的实现方式有哪些吗?
3.1.基于操作系统内核实现方式(内核线程)
3.2.基于用户进程实现方式(用户态线程,即协程)
3.3.java的线程实现方式是:内核线程实现方式
4.你知道多线程安全的三要素吗?
4.1.线程安全要素一:原子性
4.2.线程安全要素二:可见性
4.3.线程安全要素三:有序性
5.你知道java的内存模型JMM吗?
5.1.参见附图
6.你知道java编程中,线程安全的常规手段吗?
6.1.线程安全常规手段一:加锁
6.2.线程安全常规手段二:消除共享资源
7.你知道java中的volatile关键字吗?
7.1.volatile关键字是一种轻量级线程安全实现方式
7.2.volatile关键字的底层原理:保证可见性,禁止重排序
7.3.使用volatile关键字注意事项:
a.volatile关键字修饰变量值修改,不依赖原来的值;或者只有单一线程进行修改
b.volatile关键字修饰的变量,不与其它变量一起参与原子性约束
c.满足a、b两条,那么volatile关键字修饰的变量,在多线程下是线程安全的