在java多线程并发编程中,有八大基础核心。
看看都有哪八大基础核心呢?它们分别是:
1.创建线程的方式
2.线程启动
3.线程停止
4.线程生命周期
5.线程相关的方法
6.线程相关的属性
7.线程异常处理
8.线程安全
今天我们从第八个基础核心开始:线程安全
#前情回顾
1.多线程编程中,比较为难,又需要重点关注的一个话题,就是线程安全
2.需要从理论、和实战多个角度去看
3.本着一篇文章,信息量不要太多的原则
4.本篇文章仅相对全面的梳理线程安全的基础
5.更多内容,结合JUC的内容,推荐了解的内容有:
线程池、锁、CAS、ThreadLocal
并发集合、并发流程控制、AQS
#考考你
1.你知道多线程的理论基础有哪些吗?
2.你知道线程的实现方式有哪些吗?
3.你知道多线程安全的三要素吗?
4.你知道java的内存模型JMM吗?
5.你知道让线程安全的常规手段吗?
6.你知道java中的volatile关键字吗?
简述:
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(); } } } }
简述:
1.彩蛋:通过javap工具,查看字节码文件结构
2.说明i++操作,对于jvm执行子系统,不是原子性(是由多条指令组成)
3.以下是类:ThreadSafeIAddOper,对应的class文件内容
D: 3other 2studycodingmypro hread-pro argetclasses>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: 3other 2studycodingmypro hread-pro argetclasses>
简述:
1.通过截图,可以看到一个i++操作,在字节码层面,对应了四条jvm字节码指令:
getstatic、iconst_1、iadd、putstatic
2.说明对于jvm来说,i++不是原子性操作
简述:
1.改造3.1案例代码,通过加锁实现:多条指令操作的原子性。从而实现线程安全。
2.给addI方法,增加synchronized同步锁
执行结果:
简述:
改造3.1.案例代码,通过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关键字修饰的变量,在多线程下是线程安全的
java内存模型JMM:
(到这里,多线程基础编程暂时告一段落,本系列是学习笔记,参考了悟空老师的课程:《Java并发核心知识体系精讲》。欢迎大家去学习悟空老师的课程,讲的非常好!同时向悟空老师问好!)