并发编程的探索
简介
顾名思义,并发就是指当前的系统,能够同时承载的访问数量。通常会细定义为TPS与QPS
- TPS(Transaction Processing Systems): 每秒事务处理数量
- QPS(Query Processing Systems) : 每秒查询处理数量
当然,互联网上对于TPS与QPS还有更细化的解释:
Tps即每秒处理事务数,包括了
1、用户请求服务器
2、服务器自己的内部查询等处理
3、服务器返回给用户
这三个过程,每秒能够完成N个这三个过程,Tps也就是N;QPS基本类似于TPS,但是不同的是,对于一个页面的一次访问,形成一个TPS;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入QPS之中。每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准
一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
如果是对一个接口(单场景)压测,且这个接口内部不会再去请求其它接口,那么TPS等于QPS,否则,TPS不等于QPS
需求
硬件需求
一个系统能支持多少的并发量,除了本身软件的架构设计以及代码外,最重要的还是硬件的支撑,而硬件就是我们所熟知的现今计算机基本组成,即硬盘、内存、CPU、网卡以及通讯提供商给予的宽带种类、速率。当硬件达到瓶颈时我们也可以横向扩展,就是我们所说的集群。
软件需求
软件需求依托于硬件,再多的优化也只能在当前硬件的极限环境内波动,不可能超过硬件的承受范围,这个承受范围的衡量标准,就是RT(Response Time) 响应时间。软件方面的优化思路,就是要充分利用好硬件,多线程的异步执行,就是充分利用硬件提高并发方式的其中之一。
线程与进程
一个进程可以创建多个线程,而线程是操作系统进行运算和调度的最小单元,多个线程可以并行执行,而并行执行的数量,原来线程的数量由CPU的核心数决定,但是Intel的超线程技术出来后,这个概念就不再固定,变成了跟CPU线程数决定,也就是我们通常买CPU时所听说的8核16线程。
并发与并行
- 并行 并行由CPU的核心数或者线程数决定,意味着可以同时执行的任务数量
- 并发 并发则是只同一时间段内在多个线程内同时执行的数量。由于并行数量的限制,CPU则会通过时间片切换的方式来同时执行这些超过本身并行数量的任务。
时间片轮转调度
时间片轮转调度,就是我们经常说的时间片切换,引用百度百科含义就是
时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。
时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要一定时间的–保存和装入寄存器值及内存映像,更新各种表格和队列等。
时间片在没有干预的情况下,每段时间片执行时长都是固定的,进程的切换需要耗费较大的资源,而线程的切换只需保存少量寄存器的内容,但是即使这样,时间片的切换依旧需要耗费少量时间,这是不可避免地。
创建线程
博主是一名默默无闻的Java程序员,故以Java代码举例创建线程的其中三种方式
继承Thread类
public class ThreadMethod1 extends Thread {
@Override
public void run() {
System.out.println("线程启动了");
}
public static void main(String[] args) {
ThreadMethod1 threadMethod1 = new ThreadMethod1();
threadMethod1.start();
}
}
由代码可知,run方法为具体执行的逻辑,而start则为启动线程
实现Runnable接口
public class ThreadMethod2 implements Runnable {
@Override
public void run() {
System.out.println("线程-实现Runnable接口方式运行成功");
}
public static void main(String[] args) {
Thread thread = new Thread(new ThreadMethod2());
thread.start();
}
}
由代码可知,实现Runnable接口的方式仍然需要Thread类中的start方法去启动并且这两类创建方式均没有返回值,场景用于无需关心执行结果的场景。
实现Callable接口
public class ThreadMethod3 implements Callable<String> {
@Override
public String call() throws Exception {
return "当前线程执行成功,这个是返回结果";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadMethod3 threadMethod3 = new ThreadMethod3();
FutureTask<String> futureTask = new FutureTask<>(threadMethod3);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
Callable接口提供了一个带返回值的call方法,返回类型由实现Callable的泛型决定,并用FutureTask类来获取未来任务的结果。
线程的生命周期
本文已Java语言距离,在Java中,线程一共有6种状态
- New 新生
- Runnable 运行
- Blocked 阻塞
- Waiting 等待
- TimedWaiting 超时等待
- Terminated 结束