本课程将结合大量图示及代码演示,带你掌握多线程并发编程(线程安全,线程调度,线程封闭,同步容器等)与高并发处理思路与手段(扩容,缓存,队列,拆分等),构建完整的并发与高并发知识体系
课程目标
- 线程安全性
- 原子性、可见性、有序性;
- atomic包、CAS算法、synchronized与Lock、olatile、happens-before
- 安全发布对象
- 安全发布方法、不可变对象
- final关键字使用、不可变方法
- 线程不安全类与写法
- 线程安全手段
- 堆栈封闭、ThreadLocal线程封闭
- JDBC的线程封闭、同步容器
- 并发容器、J.U.C
- AQS等其他J.U.C组件
- CountDownLatch、Semaphore
- CyclicBarrier、ReentrantLock与锁
- Condition、FutureTask
- Fork/Join框架、BlockingQueue
- 线程调度(线程池)
- new Thread弊端、线程池的好处
- ThreadPoolExecutor、Executor框架接口
- 线程安全补充内容
- 死锁的产生与预防、多线程并发最佳实践
- Spring的线程安全、HashMap和ConcurrentHashMap深入讲解
课程介绍
- 第1章 课程介绍(了解本课程必看)
- 课程目标:Java并发编程入门,适合没有并发编程经验的同学,本章首先从课程重点、特点、适合人群及学习收获几个方面对课程进行整体的介绍,然后会从一个实际的计数场景实现开始,给大家展示多线程并发时的线程不安全问题,让大家能够初体验到并发编程,之后会讲解并发和高并发的概念,并通过对比让大家明白到底什么是并发
- 第2章 准备工作
- 本章主要是为课程里代码演示做必要的准备。首先会基于SpringBoot快速搭建一个方便演示的Java项目,然后简单介绍一下码云及代码的管理。项目搭建好,我会使用简单的例子演示一下并发的模拟验证,主要包括对工具Postman、JMeter、Apache Bench(AB)的使用,以及使用并发的代码来验证并发处理的正确性。
并发:
同时拥有两个或者多个线程,如果程序在单核处理器上运行多个线程将交替地换入或者换出内存,这些线程是同时"存在"的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。
高并发:高并发( High Concurrency) 是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够"同时并行处理"很多请求
并发: 多个线程操作相同的资源,保证线程安全,合理使用资源高并发: 服务能同时处理很多请求,提高程序性能
CPU多级缓存
- 左图为最简单的高速缓存的配置,数据的读取和存储都经过高速缓存,CPU核心与高速缓存有一条特殊的快速
通道;主存与高速缓存都连在系统总线上(BUS)这条总线还用于其他组件的通信- 在高速缓存出现后不久,系统变得越来越复杂,高速缓存与主存之间的速度差异被拉大,直到加入了另一级缓存,新加入的这级缓存比第一缓存更大,并且更慢,而且经济上不合适,所以有了二级缓存,甚至是三级缓存
为什么需要CPU Cache?
CPU 的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源。 所以cache 的出现,是为了缓解 CPU和内存之间速度的不匹配问题
结构:cpu-> cache-> memory
JAVA内存模型:
Heap(堆):
Java里的堆是一个运行时的数据区,堆是由垃圾回收来负责的,堆的优势是可以动态的分配内存大
小,生存期也不必事先告诉编译器,他是在运行时动态分配内存的,Java的垃圾回收器会定时收走
不用的数据,
缺点:由于要在运行时动态分配,所有存取速度可能会慢一些
Stack(栈):
栈的优势是存取速度比堆要快,仅次于计算机里的寄存器,栈的数据是可以共享的,
缺点:是存在栈数据的大小与生存期必须是确定的,缺乏一些灵活性栈中主要存放一些基本类型的
变量,比如:int、short、long、bytedouble、float、boolean、char、对象句柄,
Java内存模型要求调用栈和本地变量存放在线程栈(Thread Stack)上,对象存放在堆上。一个本地变量可能存放一个对象的引用,这时引用变量存放在本地栈上,但是对象本身存放在堆上
成员变量跟随着对象存放在堆上,而不管是原始类型还是引用类型,静态成员变量跟随着类的定义一起存在在堆上
存在堆上的对象,可以被持有这个对象的引用的线程访问
如果两个线程同时访问同一个对象的私有变量,这时他们所拥有的是"这个对象的私有拷贝"("这块要注意,很重要!!")
- 第3章 线程安全性讲解
- 本章讲解线程安全性,主要从原子性、可见性、有序性三个方面进行讲解。原子性部分,会详细讲解atomic包下相关类、CAS原理、Unsafe类、synchronized关键字等的使用及注意事项。可见性部分,主要介绍的是volatile关键字的规则和使用,及synchronized关键字的可见性。有序性部分,则重点讲解了happens-before原则。
- 第4章 安全发布对象讲解
- 本章主要讲解安全发布对象的一些核心方法,主要通过单例类的多种实现方式,让大家在实现过程中去体会这些方法的具体含义。这一章也是对线程安全性的巩固,也是把线程安全性涉及的一些关键字和类再一次放到实际场景中使用,加深大家对他们的印象和认识。
- 第5章 线程安全策略讲解
- 本章主要讲解线程安全策略,包括定义不可变对象、线程封闭、同步容器、并发容器等,引出并发里的关键知识J.U.C。同时还额外介绍了开发中常见的一些线程不安全类和写法,并给出他们各自对应的替代方案。这一章涉及的内容在日常开发和面试中都会涉及很多。
- 第6章 J.U.C之AQS讲解
- AQS是J.U.C的重要组件,也是面试的重要考点。这一章里将重点讲解AQS模型设计及相关同步组件的原理和使用,都非常实用,具体包括:CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock与锁、Condition等。这些组件需要大家能熟练明白他们的用途及差异,不但会使用,而且还要明确知道不同方法调用后的不同效果。
- 第9章 多线程并发拓展讲解
- 本章会对并发编程做些补充,但都贴近当前的面试,主要讲解死锁产生的条件及预防、多线程并发编程的最佳实践、Spring与线程安全、以及面试都特别喜欢问的HashMap和ConcurrentMap源码细节。当然,面试喜欢问的问题,对实际项目开发也是特别重要的。
- 第10章 高并发之扩容思路
- 高并发部分:主讲思路,侧重面试,本章主要讲扩容思路,首先介绍垂直扩容和水平扩容的区别,之后介绍数据库读操作扩展和写操作扩展思路。
- 第11章 高并发之缓存思路
- 高并发部分:主讲思路,本章讲解高并发中缓存方案。包含对缓存特征(命中率、最大元素、清空策略)、影响缓存命中率因素、缓存分类和应用场景(本地缓存、分布式缓存)、高并发场景下缓存常见问题(缓存一致性、缓存并发、缓存穿透、雪崩)等的具体介绍。此外,针对大家常用的缓存组件Guava Cache、Memcache、Redis
- 第12章 高并发之消息队列思路
- 高并发部分:主讲思路,本章介绍了消息队列的特性(业务无关、FIFO、容灾、性能)、为什么需要消息队列以及消息队列的好处(业务解耦、最终一致性、广播、错峰与流控),最后对当前比较流行的消息队列组件kafka和rabbitmq做了架构分析和特性介绍
- 第13章 高并发之应用拆分思路
- 本章直接从实际项目拆分步骤讲起,让大家可以实际感受到应用拆分的好处和解决的问题,之后引出对应用拆分原则(业务优先、循序渐进、兼顾技术、可靠测试)和应用拆分时思考的内容(应用之间通信、应用之间数据库设计、避免事务跨应用),并引出对服务化Dubbo和微服务Spring Cloud的框架介绍。
- 第14章 高并发之应用限流思路
- 高并发部分:主讲思路,本章从实际项目保存百万数据的限流场景开始讲起,让大家感受一下某些高并发场景下使用限流和不使用限流的区别,明确限流的重要作用。之后详细介绍了限流常用的四种算法:计数法、滑动窗口、漏桶算法和令牌桶算法,并对他们做了简单的对比。
- 第15章 服务降级与服务熔断思路
- 主讲思路,本章首先通过举例让大家明白什么是服务降级和服务熔断,之后介绍了服务降级的分类:自动降级(超时、失败次数、故障、限流)和人工降级(开关),总结了服务降级和服务熔断的共性(目的、最终表现、粒度、自治)和区别(出发原因、管理目标层次、实现方式)以及服务降级要考虑的问题。
- 第15章 服务降级与服务熔断思路
- 主讲思路,本章首先通过举例让大家明白什么是服务降级和服务熔断,之后介绍了服务降级的分类:自动降级(超时、失败次数、故障、限流)和人工降级(开关),总结了服务降级和服务熔断的共性(目的、最终表现、粒度、自治)和区别(出发原因、管理目标层次、实现方式)以及服务降级要考虑的问题。
- 第16章 数据库分库分表与高可用手段
- 高并发部分:主讲思路,本章从数据库瓶颈开始讲起,引出对数据库切库分库分表的介绍。数据库切库里重点介绍了读写分离的设计,对比支持多数据源和分库的区别;最后介绍了什么时候该考虑分表、横向分表与纵向分表,以及通过mybatis的分页插件shardbatis2.0实现数据库分表。之后介绍了高可用的三个常用手段:
- 第17章 课程总结
- 本章首先对本课程的知识进行总结回顾,然后针对面试中的并发问题与高并发问题进行提问,希望大家都能有所收获,并期待与大家共同探讨并发与高并发的话题。
从抽象的角度看 线程和主内存之间的关系
每个线程之间共享变量都存放在主内存里面,每个线程都有一个私有的本地内存
本地内存是Java内存模型中抽象的概念,并不是真实存在的(他涵盖了缓存写缓冲区。寄存器,以及其他硬件的优化)
本地内存中存储了以读或者写共享变量的拷贝的一个副本
从一个更低的层次来说,线程本地内存,他是CPU缓存,寄存器的一个抽象描述,而JVM的静态内存存储模型,
他只是一种对内存模型的物理划分而已,只局限在内存,而且只局限在JVM的内存
如果线程A和线程B要通信,必须经历两个过程:
1、A将本地内存变量刷新到主内存
2、B从主内存中读取变量
Java内存模型 - 八种同步操作
1.Lock(锁定):作用于主内存的变量,把一个变量标识变为一条线程独占状态
2.Unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
3.Read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
4.Load(载入):作用于工作内存的变量,它把Read操作从主内存中得到的变量值放入工作内存的变量副本中
5.Use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
6.Assign(赋值):作用于工作内存的变量,它把一个从执行引擎接受到的值赋值给工作内存的变量
7.Store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
8.Write(写入):作用于主内存的变量,它把Store操作从工作内存中一个变量的值传送到主内存的变量中
Java内存模型 - 同步规则
1.如果要把一个变量从主内存中赋值到工作内存,就需要按顺序得执行read和load操作,如果把变量从工作内
存中同步回主内存中,就要按顺序得执行store和write操作,但java内存模型只要求上述操作必须按顺
序执行,没有保证必须是连续执行,也就是说Read和Load、Store和Write之间是可以插入其他指令的
2.不允许read和load、store和write操作之一单独出现
3.不允许一个线程丢弃他的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中
4.不允许一个线程无原因地(也就是说必须有assgin操作)把数据从工作内存同步到主内存中
5.一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变
量。即就是对一个变量实施use和store操作之前,必须先执行过了load和assign操作
6.一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以同时被一条线程重复执行多次,多
次执行lock后,只有执行相同次数的unlock操作,变量才会解锁,lock和unlock必须成对出现
7.如果一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎中使用这个变量前需要重新执行
load或assign操作初始化变量的值
8.如果一个变量事先没有被lock操作锁定,则不允许他执行unlock操作,也不允许去unlock一个被其他线程
锁定的变量
9.对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(其实就是执行store和write操作之后)
并发的优势和风险