什么是高并发?
高并发(High Concurrency)是一种系统运行过程中遇到的一种“短时间内遇到大量操作请求”的情况,主要发生在web系统集中大量访问收到大量请求(例如:12306的抢票情况;天猫双十一活动)。该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等。
高并发的处理指标?
高并发相关常用的一些指标有:
1.响应时间(Response Time)
响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间
2.吞吐量(Throughput)
吞吐量:单位时间内处理的请求数量。
3.每秒查询率QPS(Query Per Second)
QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
4.并发用户数
并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
高并发和多线程的关系和区别
“高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则 高并发 ≠ 多线程
1.多线程
多线程是java的特性,因为现在cpu都是多核多线程的,可以同时执行几个任务,为了提高jvm的执行效率,java提供了这种多线程的机制,以增强数据处理效率。多线程对应的是cpu,高并发对应的是访问请求,可以用单线程处理所有访问请求,也可以用多线程同时处理访问请求。
在过去单CPU时代,单任务在一个时间点只能执行单一程序。之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程。虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个CPU,并交由操作系统来完成多任务间对CPU的运行切换,以使得每个任务都有机会获得一定的时间片运行。
再后来发展到多线程技术,使得在一个程序内部能拥有多个线程并行执行。一个线程的执行可以被认为是一个CPU在执行该程序。当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序。
总之,多线程即可以这么理解:多线程是处理高并发的一种编程方法,即并发需要用多线程实现。
2.高并发
高并发不是JAVA的专有的东西,是语言无关的广义的,为提供更好互联网服务而提出的概念。
典型的场景,例如:12306抢火车票,天猫双十一秒杀活动等。该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等。如果高并发处理不好,不仅仅降低了用户的体验度(请求响应时间过长),同时可能导致系统宕机,严重的甚至导致OOM异常,系统停止工作等。
如果要想系统能够适应高并发状态,则需要从各个方面进行系统优化,包括,硬件、网络、系统架构、开发语言的选取、数据结构的运用、算法优化、数据库优化等……而
多线程只是其中解决方法之一。多线程并发技术
java多线程编程将会涉及到如下技术点:
1.并发编程三要素
- 原子性原子,即一个不可再被分割的颗粒。在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
- 有序性程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
- 可见性当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。
2. 线程的五大状态
- 创建状态当用 new 操作符创建一个线程的时候
- 就绪状态调用 start 方法,处于就绪状态的线程并不一定马上就会执行 run 方法,还需要等待CPU的调度
- 运行状态CPU 开始调度线程,并开始执行 run 方法
- 阻塞状态线程的执行过程中由于一些原因进入阻塞状态比如:调用 sleep 方法、尝试去得到一个锁等等
- 死亡状态run 方法执行完 或者 执行过程中遇到了一个异常
3.悲观锁与乐观锁
- 悲观锁:每次操作都会加锁,会造成线程阻塞。
- 乐观锁:每次操作不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。
4.线程之间的协作:wait/notify/notifyAll等
5.synchronized 关键字
6.CAS
CAS全称是Compare And Swap,即比较替换,是实现并发应用到的一种技术。操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
7.线程池
如果我们使用线程的时候就去创建一个线程,虽然简单,但是存在很大的问题。如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池通过复用可以大大减少线程频繁创建与销毁带来的性能上的损耗。
高并发技术方案:提高高并发能力
1.分布式缓存:redis、memcached等,结合CDN来解决图片文件等访问。
2.消息队列中间件:activeMQ等,解决大量消息的异步处理能力。
3.应用拆分:一个工程被拆分为多个工程部署,利用dubbo解决多工程之间的通信。
4.数据库垂直拆分和水平拆分(分库分表)等。
5.数据库读写分离,解决大数据的查询问题。
6.还可以利用nosql ,例如mongoDB配合mysql组合使用。
7.还需要建立大数据访问情况下的服务降级以及限流机制等。
6个并发问题引发的血案
1、synchronized的CPU原语级别是如何实现的?2、一千万个数,如何高效求和。3、不用数学库,求2开平方的值,精确到小数点儿后10位。4、线程A不断打印1-10的数字,打印到第5个数字时通知线程B,请完成编码。5、下列三种业务,应该如何使用线程池:高并发、任务执行时间短的业务并发不高、任务执行时间长的业务并发高、业务执行时间长的业务6、你如何来设计12306网站,能够撑住最高百万级别TPS(淘宝最高54万TPS)?
以上6 个并发问题,一个都答不上来?强烈建议来看看这本马士兵的「多线程与高并发」书籍
给大家看看这本书籍的大概内容
目录:
- 第一节:线程的基本概念
- 第二节:volatile与CAS
- 第三节:Atomic类和线程同步新机制
- 第四节:LockSupport、淘宝面试题与源码阅读方法论
- 第五节:AQS源码阅读与强软弱虚4种引用以及ThreadLocal原理与源码
- 第六节:并发容器
- 第七节:线程池
- 第八节:线程池与源码阅读
- 第九节:JMH与Disruptor
第一节:线程的基本概念
线程与高并发大概讲六大块
- 第一:基本的概念,从什么是线程开始
- 第二:JUC同步工具,就是各种同步锁
- 第三:同步容器
- 第四:线程池
- 第五:高频面试加分项的一些面试用的东西,包括纤程
- 第六:Disruptor,不知道有多少同学听说过这个框架的,这个框架它也是一个MQ框架
- (Message Queue)叫做消息队列,消息队列非常多,后面还会给大家讲Kafka、RabbitMQ,
- Redis等这些都是消息队列。Disruptor是目前大家公认的在单机环境上效率最高的、性能最快的
- MQ。
我们先说一下为什么要讲多线程和高并发?
- 原因是,你想拿到一个更高的薪水,在面试的时候呈现出了两个方向的现象:
- 第一个是上天
- 项目经验
- 高并发 缓存 大流量 大数据量的架构设计
- 第二个是入地
- 各种基础算法,各种基础的数据结构
- JVM OS 线程 IO等内容
- 多线程和高并发,就是入地里面的内容。
基本概念
我们先从线程的基本概念开始,给大家复习一下,不知道有多少同学是基础不太好,说什么是线程都不
知道的,如果这样的话,花时间去补初级内容的课
第二节:volatile与CAS
volatile
我们先来看这个volatile的概念,volatile它是什么意思,现在像大的互联网企业的面试,基本上volatile
是必会的,有时候他也不会太问,认为你应该会,但是中小企业也就开始问这方面的问题。
我们来看一下这个小程序,写了一个方法啊,首先定义了一个变量布尔类型等于true,这里模拟的是一
个服务器的操作,我的值为true你就给我不间断的运行,什么时候为false你再停止。 测试new Thread
启动一个线程,调用m方法,睡了一秒,最后running等于false,运行方法他是不会停止的。 如果你要
把volatile打开,那么结果就是启动程序一秒之后他就会m end停止。(volatile就是不停的追踪这个
值,时刻看什么时候发生了变化)
CAS
cas号称是无锁优化,或者叫自旋。这个名字无所谓,理解它是干什么的就行,概念这个东西是人为了
描述问题解决问题而定义出来的,所以怎么定义不是很重要,重点是在解决问题上
我们通过Atomic类(原子的)。由于某一些特别常见的操作,老是来回的加锁,加锁的情况特别多,所
以干脆java就提供了这些常见的操作这么一些个类,这些类的内部就自动带了锁,当然这些锁的实现并
不是synchronized重量级锁,而是CAS的操作来实现的(号称无锁)。
我们来举例几个简单的例子,凡是以Atomic开头的都是用CAS这种操作来保证线程安全的这么一些个
类。AtomicInteger的意思就是里面包了一个Int类型,这个int类型的自增 count++ 是线程安全的,还有
拿值等等是线程安全的,由于我们在工作开发中经常性的有那种需求,一个值所有的线程共同访问它往
上递增 ,所以jdk专门提供了这样的一些类。使用方法AtomicInteger如下代码
<code>**/<code>
<code>* 解决同样的问题的高效方法,使用AtomXXX类/<code>
<code>* AtomXXX类的本身方法都是原子性的,但不能保证多个方法连续调用都是原子性的/<code>
<code>* @author mashibing/<code>
<code>*//<code>
<code>package com.mashibing.juc.c_018_00_AtomicXXX;/<code>
<code>import java.util.ArrayList;/<code>
<code>import java.util.List;/<code>
<code>import java.util.concurrent.atomic.AtomicInteger;/<code>
<code>好,我们来分许分析,它的内部实现的原理,主要是聊原理,它的用法看看API都会用。这个原理叫/<code>
<code>CAS操作,incrementAndGet() 调用了getAndAddInt/<code>
<code>当然这个也是一个CompareAndSetInt操作/<code>
<code>public class T01_AtomicInteger {/<code>
<code>/*volatile*/ //int count1 = 0;/<code>
<code>AtomicInteger count = new AtomicInteger(0);/<code>
<code>/*synchronized*/ void m() {/<code>
<code>for (int i = 0; i < 10000; i++)/<code>
<code>//if count1.get() < 1000/<code>
<code>count.incrementAndGet(); //count1++/<code>
<code>}/<code>
<code>public static void main(String[] args) {/<code>
<code>T01_AtomicInteger t = new T01_AtomicInteger();/<code>
<code>List<thread> threads = new ArrayList<thread>();/<thread>/<thread>/<code>
<code>for (int i = 0; i < 10; i++) {/<code>
<code>threads.add(new Thread(t::m, "thread-" + i));/<code>
<code>}/<code>
<code>threads.forEach((o) -> o.start());/<code>
<code>threads.forEach((o) -> {/<code>
<code>try {/<code>
<code>o.join();/<code>
<code>} catch (InterruptedException e) {/<code>
<code>e.printStackTrace();/<code>
<code>}/<code>
<code>});/<code>
<code>System.out.println(t.count);/<code>
<code>}/<code>
<code>}/<code>
第三节:Atomic类和线程同步新机制
今天,我们继续讲一个Atomic的问题,然后开始讲除synchronized之外的别的锁。在前面内容我们讲
了synchronized、volatile、Atomic和CAS,Atomic我们只是讲了一个开头还没有讲完,今天我们继
续。
像原来我们写m++你得加锁,在多线程访问的情况下,那现在我们可以用AtomicInteger了,它内部就
已经帮我们实现了原子操作,直接写 count.incrementAndGet(); //count1++ 这个就相当于count++。原
来我们对count是需要加锁的,现在就不需要加锁了。
我们看下面小程序,模拟,我们计一个数,所有的线程都要共同访问这个数count值,大家知道如果所
有线程都要访问这个数的时候,如果每个线程给它往上加了10000,你这个时候是需要加锁的,不加锁
会出问题。但是,你把它改成AtomicInteger之后就不用在做加锁的操作了,因为incrementAndGet内
部用了cas操作,直接无锁的操作往上递增,有同学可能会讲为什么要用无锁操作啊,原因是无锁的操
作效率会更高。
第四节:LockSupport、淘宝面试题与源码阅读方法论
首先我们简单回顾一下前面三节课讲的内容,分别有线程的基本概念、synchronized、volatile、
AtomicXXX、各种JUC同步框架(ReentrantLock、CountDownLatch、CyclicBarrier、Phaser、
ReadWriteLock-StampedLock、Semaphore、Exchanger、LockSupport),其中synchornized重点讲
了一下,包括有synchornized的底层实现原理、锁升级的概念(四种状态:无锁、偏向锁、轻量级锁、
重量级锁),volatile我们讲了可见性和禁止指令重排序如何实现。
synchronized和ReentrantLock的不同?
synchronized:系统自带、系统自动加锁,自动解锁、不可以出现多个不同的等待队列、默认进行
四种锁状态的升级
ReentrantLock:需要手动枷锁,手动解锁、可以出现多个不同的等待队列、CIS的实现
本章我们补一个小漏洞,它叫LockSupport,然后我们分析两道面试题,紧接着我会教大家阅读源码的
技巧,源码层出不穷,生生不息,掌握了源码的阅读技巧,大家培养出了阅读源码兴趣的时候,之后好
多代码,你需要自己去抠,抠出来才是你自己的,最后我们会分析AQS源码,以上是我们本章主讲的内
容概述。
LockSupport
我们会以几个小程序为案例,展开对LockSupport的讲解,在以前我们要阻塞和唤醒某一个具体的线程
有很多限制比如:
1、因为wait()方法需要释放锁,所以必须在synchronized中使用,否则会抛出异常
IllegalMonitorStateException
2、notify()方法也必须在synchronized中使用,并且应该指定对象
3、synchronized()、wait()、notify()对象必须一致,一个synchronized()代码块中只能有一个线程调
用wait()或notify()
以上诸多限制,体现出了很多的不足,所以LockSupport的好处就体现出来了。
在JDK1.6中的java.util.concurrent的子包locks中引了LockSupport这个API,LockSupport是一个比较
底层的工具类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS:
AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()的方
法,来实现线程的阻塞和唤醒的。我们先来看一个小程序:
后续的内容给大家大概截个图,由于内容太多了
第五节:AQS源码阅读与强软弱虚4种引用以及ThreadLocal原理与源码
第六节:并发容器
今天是第六天了,这节课本来想上一个大而全的课,后来发现这个实在目标太大了,大而全的概念就是
上节课讲到的那张容器图中的每一个都讲的非常的细致,然后去谈他们的源码。但是如果这么讲的话我
们高并发的课就讲不完了,所以也别着急,后面单独开一门课来讲集合,集合的发展历程,现在为什么
讲这个并发容器呢,主要是为了线程池做准备,线程池里有一个参数就是用并发容器来做你工作任务的
容器。
第七节:线程池
第八节:线程池与源码阅读
第九节:JMH与Disruptor
如何获取?
转发这篇文章,关注我,私信回复“学习”即可获取电子书籍,以上 spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构
如何私信?
关注我后,在手机,点进我的主页,主页上方右上角有个私信,点击私信,如何回复关键字“学习”即可
閱讀更多 JAVA架構公社師長 的文章