QPS = 并发量 / 平均响应时间
并发量 = QPS * 平均响应时间
根据以上计算关系,我们来预估下单日访问量在 1000W 需要多大的QPS来支持:
通常情况下,80% 的访问量集中在 20%的时间,算一下这 1000w pv(page view,页面浏览量) 实际需要机器达到多少qps才能满足,
qps = (1000w * 0.8) / (24 * 3600 * 0.2)
qps = 462.9
根据压力测试的反馈,单台机子的QPS是多少,利用以上结果就可以算出需要几台机器
每个进程都有独立的代码和数据空间。线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间。
最根本区别:进程是资源分配的单位,线程是调度和执行的单位
多进程:在操作系统中能同时运行多个任务(程序)
多线程:在同一应用程序中有多个顺序流同时进行
程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址
栈:用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯
堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的
方法区:则用来存放 NM 加载的类、常量及静态变量等信息,也是线程共享的
- 让当前线程休眠指定时间。
- 休眠时间的准确性依赖于系统时钟和CPU调度机制。
- 不释放已获取的锁资源,如果sleep方法在同步上下文中调用,那么其他线程是无法进入到当前同步块或者同步方法中的。
- 可通过调用interrupt()方法来唤醒休眠线程。
- 让当前线程进入等待状态,当别的其他线程调用notify()或者notifyAll()方法时,当前线程进入就绪状态
- wait方法必须在同步上下文中调用,例如:同步方法块或者同步方法中,这也就意味着如果你想要调用wait方法,前提是必须获取对象上的锁资源
- 当wait方法调用时,当前线程将会释放已获取的对象锁资源,并进入等待队列,其他线程就可以尝试获取对象上的锁资源。
- 和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁。
- 和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间。
- 还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会
- 等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景
每个对象有一个监视器锁(Monitor),当 Monitor 被占用时就会处于锁定状态。
线程执行Monitorenter 指令时尝试获取Monitor的所有权,过程如下:
-
如果 Monitor 的进入数为 0,则该线程进入 Monitor,然后将进入数设置为 1,该线程即为 Monitor 的所有者。
-
如果线程已经占有该 Monitor,只是重新进入,则进入 Monitor 的进入数加 1。(可重入锁)
-
如果其他线程已经占用了 Monitor,则该线程进入阻塞状态,直到 Monitor 的进入数为 0,再重新尝试获取 Monitor 的所有权。
相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。
JVM 就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置。
如果设置了,执行线程将先获取 Monitor,获取成功之后才能执行方法体,方法执行完后再释放 Monitor。在方法执行期间,其他任何线程都无法再获得同一个 Monitor 对象。
其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。如果任何一步操作没有完成,那么所有完成的步骤都必须回滚
CAS是乐观锁技术,是原子操作,非阻塞算法。
在线程开启的时候,会从主存中给每个线程拷贝一个变量副本到线程各自的运行环境中,CAS算法中包含三个参数(V,E,N),V表示要更新的变量(也就是从主存中拷贝过来的值)、E表示预期的值、N表示新值。
假如现在有两个线程t1,t2,,他们各自的运行环境中都有共享变量的副本V1、V2,预期值E1、E2,预期主存中的值还没有被改变,假设现在在并发环境,并且t1先拿到了执行权限,失败的线程并不会被挂起(非阻塞),而是被告知这次竞争中失败,并可以再次发起尝试,然后t1比较预期值E1和主存中的V,发现E1=V,说明预期值是正确的,执行N1=V1+1,并将N1的值传入主存。这时候贮存中的V=21,然后t2又紧接着拿到了执行权,比较E2和主存V的值,由于V已经被t1改为21,所以E2!=V,t2线程将主存中已经改变的值更新到自己的副本中,再发起重试;直到预期值等于主存中的值,说明没有别的线程对旧值进行修改,继续执行代码,退出;
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长开销很大。
- 我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作。
- 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
- ABA问题。
- 如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?(可能刚好被改回来了)
优点:
可以避免优先级倒置和死锁(因为失败不会阻塞,而是重新尝试)等危险,竞争比较便宜,协调发生在更细的粒度级别,允许更高程度的并行机制等等
JAVA对CAS的支持:
在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的。相对于Synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。
ABA问题的根本在于CAS在修改变量的时候,无法记录变量的状态,比如修改的次数,是否修改过这个变量。这样就很容易在一个线程将A修改成B时,另一个线程又会把B修改成A,造成CAS多次执行的问题。
https://www.cnblogs.com/lfs2640666960/p/11019798.html
计算机在执行程序的时候,每条指令都是在CPU中执行的,而执行的时候,要和数据打交道。而计算机上面的数据,是存放在主存当中的,也就是计算机的物理内存
随着CPU技术的发展,CPU执行速度越来越快。内存的技术并没有太大的变化,所以从内存中读取和写入数据的过程和CPU的执行速度比起来差距就会越来越大,导致CPU每次操作内存都要耗费很多等待时间。
在CPU和内存之间增加高速缓存。缓存就是保存一份数据拷贝。特点是速度快,内存小,并且昂贵。
当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存中。
随着CPU能力的不断提升,一层缓存就无法满足要求了,衍生出多级缓存。每一级缓存中所储存的全部数据都是下一级缓存的一部分。
当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。
单核CPU只含有一套L1,L2,L3缓存,多核CPU,则每个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。
单线程:
- cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。
单核CPU,多线程:
- 进程中的多个线程会同时访问进程中的共享数据,CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。但由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。
多核CPU,多线程:
- 每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的cache中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。
在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。
为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化
除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排。
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性即程序执行的顺序按照代码的先后顺序执行。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
而JMM Java Memory Model就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
原子性
- 在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现原理中有讲解,这两个字节码,在Java中对应的关键字就是synchronized。
可见性
- Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。
有序性
- 在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:
- volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作
https://www.jianshu.com/p/f74044782927
https://blog.csdn.net/zjkC050818/article/details/78345819?locationNum=9&fps=1
https://www.jianshu.com/p/7b1c8093ddca
常见面试题 https://blog.csdn.net/weixin_42716620/article/details/82888576
HTTP与HTTPS的区别
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
- get和post最明显的区别就是,get把参数写在url中,而post则是把参数写在request body中
- GET 请求可被缓存,POST请求不会被缓存
- GET 请求长度有限制,POST 请求没有限制
- 先来先服务调度算法
- 短作业(进程)优先调度算法
- 高优先权优先调度算法,优先级
- 高响应比优先调度算法 : 响应时间(等待时间+服务时间)/服务时间
- 基于时间片的轮转调度算法
https://blog.csdn.net/qq_29996285/article/details/88078906
https://blog.csdn.net/qq_36071795/article/details/83956068
https://www.cnblogs.com/51life/p/10303696.html
https://blog.csdn.net/xiaojin21cen/article/details/99827577
三次握手和四次挥手
https://blog.csdn.net/qq_39192189/article/details/81428551
为什么tcp可靠
https://blog.csdn.net/Awille/article/details/79748193
https://www.zhihu.com/question/24853633
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
https://blog.csdn.net/lengxiao1993/article/details/82771768
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
如果是两次握手,当c给s发送连接请求,s收到后给c发送确认响应,此时s就认为已经建立连接了,但如果该确认响应丢失了,c就不知道s是否收到自己的连接请求,就会关闭不接受后面的数据报文,而s就会向c发送报文浪费资源
https://www.nowcoder.com/tutorial/94/a6035e5453f946aba0615705f94ca1e2
https://www.cnblogs.com/zpcoding/p/10542470.html
https://blog.csdn.net/qq_35210580/article/details/98958092
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。