并发编程大纲
https://www.jianshu.com/p/2075b452bc46
并发编程
为什么需要并发编程
目的
提高系统性能
本质
软件是一组特定计算机指令的集合,是架构在硬件之上的对特定业务的一种表达和计算的抽象;所以并发编程底层的焦点就是如何执行;也就是cpu如果管理指令的执行;cpu时间分片来管理线程执行
并发编程优势
-
资源利用率
cpu 多核资源;线程运行到需要等待的时候,等待的同时,执行其它程序
-
公平性
用户和程序对计算机拥有同等的使用权;一种高效的运行方式就是通过粗粒度的时间分片,是这些用户和程序能共享计算机资源
-
便利性
建模简单性、异步事件的简化
-
提高相应速度
编发编程带来的问题
-
线程安全问题
共享变量的控制
-
活跃性
线程安全关注的是程序不会产生意想不到的结果;活跃性关注的是线程总是向正确的方向执行;
所以会出现死锁(资源共享竞争)、饥饿(线程永远执行不到,优先级底等)、活锁(没有导致阻塞,但是重复尝试-失败-尝试-失败)等现象
-
性能问题
活跃性意味着某件正确的事情最终会发生;而性能则是关注是希望正确的事情尽快的发生。
线程安全
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类时线程安全的。
编写线程安全代码核心
对状态的操作进行管理;特别是对共享的和可变的状态访问
共享:可以由多个线程同时访问
可变:变量的值在其生命周期内可以发成变化
一个对象是否需要时线程安全的,取决于它是否被多个线程访问
线程安全-共享变量控制
- 不在线程之间共享该状态变量
- 将状态变量修改为不可变的变量
- 在访问状态变量时使用同步
没有共享变量的对象,称为无状态对象;无状态对象一定是线程安全的
如何保证线程安全
-
同步
-
加锁
-
volatile (cas)
加锁机制既可以保证可见性又可以确保原子性,而volatile变量只能确保可见性
避免使用同步的方式就是不共享数据(线程封闭)
线程封闭:封闭在线程中的对象不会从线程中逸出(如:线程池、ThreadLocal)
- 栈封闭:只能通过局部变量才能访问对象(基本类型)
- ThreadLocal
-
-
使用不可变对象
不可变对象,只有一种状态,并且该状态有构造函数来控制
-
原子性
竟态条件:不恰当的执行时序而出现不正确的结果(共享状态发生了竞争)
常见的竟态条件类型就是"先检查后执行"(如单例)
如何避免竞态条件
复合操作:包含了一组必须以原子方式执行的操作以确保线程安全(先检查后执行以原子方式执行,单例的双重校验)
如何保证原子性呢
锁来保护状态
内置锁(Synchronized)
同步(Synchronized)除了实现原子性,还有另外一个重要的方面:内存可见性
加锁的含义不仅仅局限于互斥行为,还包括内存可见性
-
可见性
锁
volatile
final (安全发布)
-
顺序性
happen-before
发布对象
发布对象:是一个对象能够被当前范围之外的代码所使用
对象逃逸:一种错误的发布。当一个对象还没有构建完成时,就使他被其它线程所见
安全发布对象
- 在静态初始化块函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
知识体系
- 线程安全
- 线程封闭
- 线程调度
- 同步容器
- 并发容器
- JMM
- AQS
- JUC
高并发解决思路
- 扩容
- 缓存
- 队列
- 拆分
- 服务降级与熔断
- 数据库分库分表
概念
并发
同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替执行,这些线程是同时“存在”的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。
高并发
高并发(High Concurrency)是互联网分布式系统架构中必须考虑的因素之一,通常指,通过设计保证系统同时并行处理很多请求。
并发和高并发区别
并发:多个线程操作相同的资源,保证线程安全,合理使用资源
高并发:服务能同时处理很多请求,提供程序性能
JMM(Java Memory Model)规定
规定了一个线程如何和何时由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量
jvm堆:java运行时的数据区,由垃圾回收来负责的;可以动态分配内存大小,对象生存期无效告诉编译器;正是因为动态分配内存,所以存储数据会慢些
栈:存取速度比堆要快,仅此与计算机的寄存器;主要存储基本变量和堆中的引用地址
java内存模型的-同步八种操作
lock(锁定):作用与主内存的变量,把一个变量表示为一条线程独占状态
unlock(解锁):作用与主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才以被其它线程锁定
read(读取):作用与主内存的变量,把一个变量从主内存传入到线程的工作内存中,以便随后的load动作使用
load(载入):作用与工作内存的变量,它把read操作从从内存中得到的变量放入工作内存的变量副本中
use(使用):作用与工作内存的变量,把工作内存中的一个变量值传递给执行引擎
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中
并发测试工具
- Postman: http请求模拟工具
- Apache Bench(AB):Apache附带的工具,测试网站性能
- Jmetter:Apache组织开发的压力测试工具
线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就成这个类时线程安全的。
原子性:提供互斥访问,同一时刻只能有一个线程来对它进行操作
可见性:一个线程对主内存的修改可以及时的被其它线程观察到
有序性:一个线程观察其它线程中的指令执行顺序,由于指令重排序的存在,该观察结构一般杂乱无序