其实之前一直想专门写一篇,单独说一说Java的多线程与高并发,但是一直以来,都没有想到能够用什么比较有趣的表现形式去表达出来,而且网上充斥着很多类似的博客,有好的又不好的,有简介的有繁琐的,所以也一直没写。
但是想了想既然之前有这个想法,而且也已经好久没有写过博客了,索性还是写一写,尽量写的有意思一点。
另:之前的高并发&性能优化没有来得及往下写,实在是因为里面的东西太过于复杂,且最近正好换了工作,确实没有那么多时间去研究,现在写东西还是希望能多写点有用的,而不是书本照搬当笔记用,当然能力有限,写烂了,大家谅解一二,略过不看即可。
当然第一篇,我们依旧从概念开始,所以第一部分仍是概念。
【并发与并行】
从题目名词开始讲。
- 并发
并发,顾名思义,一起出发;
在你吃饭的时候,来了一个电话,如果你可以先接电话,然后再继续把饭吃完,这个叫并发;
但是如果你只能等饭吃完才可以去接电话,叫非并发(串行)。
所以,并发指的是处理多任务的能力,当你只能一件事情一件事情串行执行任务的时候,就是不支持并发的,当你可以多件事情一起执行的时候(轮替或者其他方式),就是支持并发的。
- 并行
还是举上面那个例子,当你吃饭的时候,来了一个电话,你边吃饭边接电话,这叫并行;
并行指的就是同时运行;支持并行的基础就是多线程。
【同步和异步】
同步和异步的概念一般用于方法。
- 同步
当一个方法开始执行,必须等这个方法执行结束,才可以往下执行,我们叫做同步。
同步主要用于上下有递进关系的代码,特点是有序,串行执行,逻辑简单,但是执行效率较低。
- 异步
当一个方法开始执行,我们不必等这个方法执行结束,直接执行后面的内容,我们叫做异步。
(可以认为只是进行了一个消息的传递,调用后会立即返回)
异步在java里面主要使用线程(包括一些封装类也是如此)实现,特点是执行效率高,但是逻辑相对复杂,容易出问题。
【什么是高并发】
有果必有因,通俗来讲,多线程可以认为是高并发的一种表现形式或者解决方案,所以在讲多线程之前,我们先讲高并发。
高并发,指的是一个系统,在短时间内,收到大量操作请求的情况。
这种情况,一般而言主要发生在web系统中,比如:京东双十一,微博明星传出绯闻,12306春运抢票等等。
很容易理解的东西我们不过多的作诠释,以下几项,是高并发的常用指标:
- 响应时间(Response Time)
系统对请求作出的响应时间(一个请求从请求发出到请求结束的时间)
- 吞吐量(Throughput)
单位时间内处理的请求数量
- 每秒查询率QPS(Query Per Second)
每秒响应请求数。(其实与吞吐量指向同一个指标)
- 并发用户数
同时承载正常使用系统功能的用户数量。
这里需要注意的是,我们经常会将高并发和多线程放在一起讲,但是他们之间并不能划等号,多线程只是高并发在应用代码层面的一种解决方案,然而一般情况下,高并发还需要系统架构,硬件设施,网络等多方面的调优协助完成。
【雪崩效应】
雪崩效应,原本出现在密码学中,后来引申入高并发场景的一个概念。
在密码学中,雪崩效应(Avalanche effect)指加密算法(尤其是块密码和加密散列函数)的一种理想属性。
雪崩效应是指当输入发生最微小的改变(例如,反转一个二进制位)时,也会导致输出的剧变(如,输出中一半的二进制位发生反转)。
服务雪崩效应是一种因"服务提供者的不可用"(原因)导致"服务调用者不可用"(结果),并将不可用逐渐放大的现象。
服务雪崩的过程可以分为三个阶段:
- 服务提供者不可用;
- 重试加大请求流量;
- 服务调用者不可用;
------如何避免
横向扩充服务------现在我们可以利用很多工具来保证服务不会挂掉,然后流量比较大的时候,可以横向扩充服务来保证业务的流畅。
限流(下个部分会讲)
熔断(下个部分会讲)
【高并发的四大利器】
对于软件系统而言,一般会有四大策略去保证应用的高并发:
- 缓存(cache)
把常用数据存储到可以快速获取的区域(缓存区),以便重复利用,提高效率。
例如:从内存中读取数据时,先将常用的数据存放到缓存区,硬盘直接从缓存区读取。
- 降级
当服务出现问题或影响到核心流程时,需要暂时屏蔽掉,待高峰过后或问题解决后再打开;
- 限流
限流是高并发里面最重要也是最复杂的方法,当不可降级场景出现时,需要采用限流限制该场景的并发请求,有损服务而不是不服务。
通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级。
- 超过阈值时策略:
定向到错误页或告知没有资源
返回兜底数据或默认数据,如商品详情页库存默认有货
- 常见限流场景:
线程池
数据库连接池
并发请求数
接口调用速率
MQ的消费速率
- 常见限流算法:
令牌桶:一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,请求获取令牌,令牌不足时拒绝请求。
漏桶:流入速率过快,超过桶的容量,拒绝请求。
计数器(简单粗暴):当请求超过计数时,拒绝请求。
- 熔断
降级往往代表系统功能部分不可用,熔断代表的是完全不可用。
降级一般是客户端处理,熔断是在服务端处理的。
服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。
【进程】
首先我们看一下百度百科的解释:
其实用大白话讲,进程其实就是指在系统中正在运行的一个应用程序;
比如:我们电脑中运行的QQ,微信,LOL,都是一个进程。
进程主要有以下几个特性:
- 独立性
进程是系统中独立存在的实体,它拥有自己独立的资源和自己私有的地址空间。
进程之间不可以直接访问资源和地址空间。
- 动态性
程序(App)是一个静态的指令集和,进程是一个正在执行中的指令集合,进程拥有自己的生命周期和不同的生命形态。
- 并发性
多个进程可以在单个处理器上并发执行,不会相互受到影响。(主要是依赖于线程和时间片)
一个进程里面可以由单个或者多个线程协同 完成任务。
【什么是多线程】
- 首先,什么是线程?
教科书说法,线程是操作系统能够进行运算调度的最小单元。
一个进程可以有多个线程,但是一个线程只有一个父进程;
线程可以拥有自己的堆栈,程序计数器以及局部变量,但是不拥有系统资源。
其实呢,学过操作系统大家都知道,其实对于单核单CPU而言,同时是只能运行一个任务的,也就是说,同时只能跑一个线程;
如果咱们的CPU只能线性执行,就是当你运行一个线程的时候,这个线程可能要等待网络,IO等相关的资源,这个时候CPU只能等待,这样CPU强大的运算能力就没有得到发挥,所以,产生了一个时间片的概念;
- 时间片
CPU给每个线程分配了一部分时间去运行,虽然CPU同时只能运行一个线程,但是我们进行线程的快速切换之后,可以模拟出一个CPU同时运行多个线程的场景(其实主要还是CPU太快了),这样的话可以充分利用CPU计算速度快的优势。(对于时间片,有很多种不同的算法,有兴趣可以百度,这里不讲)
在引入时间片以后,咱们一块CPU就可以同时跑多个线程了。
所以什么是多线程呢?
【线程的状态】
其实下面这些随便找个教科书或者网上的教程都有,属于废话,但是为了概念的完整性,还是把它们贴在下面:
只需要关注带颜色的内容
- 新建状态(New)
线程对象被创建后,就进入了新建状态。
- 就绪状态(Runnable)
也被称为"可执行状态"。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。
- 运行状态(Running)
线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked)
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
直到线程进入就绪状态,才有机会转到运行状态。
- 等待阻塞
通过调用线程的wait()方法,让线程等待某工作的完成。
- 同步阻塞
线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- 其他阻塞
通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。
- 死亡状态(Dead)
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
【多线程三要素】
- 原子性
即一个不可再被分割的颗粒。
在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
经典场景:张三向李四转账,扣钱和入钱操作,要么全部完成,要么全部完不成。
- 有序性
程序按照代码的先后顺序执行。
这个主要是因为CPU本身可能会对指令进行重排序,在某些需要严格控制顺序的代码中,需要保持其有序。
- 可见性
多个线程同时访问某个变量的时候,其中一个线程对变量进行了修改,这个变量的新值可以马上同步到另一个线程。
关于如何保障以上三要素,后面会讲到。
关于高并发与多线程相关的概念,主要就是以上这些,在之后的内容中,会继续写到线程的实现方式和主要方法,锁,多线程中的封装类等相关内容,感谢。