深入为你探讨!并发的起源和价值

本文介绍了高并发的概念及其好处,如TPS和QPS作为衡量标准。讨论了硬件和软件资源在处理高并发时的角色,强调了线程在并发执行中的重要性。线程通过异步和并行提高性能,文章提供了Java中实现线程的方法,并解释了线程生命周期。此外,还探讨了如何通过线程池和线程状态排查问题,包括CPU占用过高和响应延迟的情况。最后,给出了排查线程问题的步骤和工具使用方法。
摘要由CSDN通过智能技术生成

本篇从为什么使用高并发,以及高并发带给我们什么好处展开进行阐述,说到高并发就不能不说线程,所以会穿插这一些线程的demo。这里只是进行浅谈,之后会进行深入的讨论,so began.

并发

【高并发】:当前系统能够同时承载的并发数,例如,我们打开一个前端页面,这个前端页面会渲染很多数据,如果有10w个用户同时访问网站进行渲染,那证明整个系统要同时支持10w个并发量。我们通常通过TPS 和QPS 去描述系统的并发数

  • TPS(Transactions Per Second): 每秒的事务处理数量,简而言之:用户请求页面->服务器进行处理->用户收到结果(这是一个TPS)
  • QPS(Queries Per Second):每秒处理的查询数量:1000个用户同时查询一个商品,1000个用户同时可以查询到信息,那么我们的1000QPS/S

如何处理高并发

  • 硬件资源
  • cpu:核心数(当前程序能够同时并行的任务数)

内存:IO性能,比如一些中间件,把数据缓存在中间件中可以减少对数据库的访问压力。

磁盘:用一些高效的读写网卡SSD

网卡:决定每次传输的数据的大小

  • 软件资源(up to 我们如何更合理的利用硬件资源)

CPU(线程):如果是8核cpu那说明可以同时运行8个线程

  • IO: 和数据库的交互:减少使用io的频率

比如说分库分表,就是因为数据量太大,导致io时间过长

分布式缓存:实质上是数据的关系型数据经过计算放在缓存中,这样就可以减少计算的数据,以及去数据库查询数据的时间

分布式消息中间件:比如注册,那就可以放在一个中间件中去跑,然后直接告诉用户已经成功,这种异步的方式就可以减少IO带来的性能损耗

so on…

  • 单节点:实际上随着硬件的提升,对我们的程序的运行效率会愈来愈小,那我们就可以进行多个单节点计算,通过多个计算机,组成一个分布式计算机:简而言:之前我们的多个任务放在同一个服务器进行计算,现在不同的服务器进行不同的任务计算,这样就减少了硬件瓶颈.

多线程

【线程】:我们来捋一下一个java程序的运行:.java源文件(磁盘中)->JVM(编译.class)->main方法进行运行,然后计算机中就产生了一个进程去运行你所写的程序,

假设你的程序中有个加载磁盘上的文件保存在数据库的操作,磁盘的IO和cpu的速度不成比例的,换而言之,io太慢了,但是cpu还需要进行等待这个io的执行,那就势必造成了cpu资源的浪费。

试想:某一进行造成了阻塞,我们是否可以让其他进程去运行呢?所以先后就有多道程序设计、分时系统、但是这些不能很好的解决问题,这个时候线程就应运而生!一个进程中可以有多个线程,

举个例子,我们在编写word文档的时候有没有发现他会自己进行保存,那这就是后台有线程在执行保存的这个操作,但是你同时可以对你的文件进行别的操作,这就是在同一个word文档的进程中,有多个线程。

但是为什么线程可以提升我们的性能呢?如下:

线程的特征

  • 异步:比如我们需要对一个超级大的文件进行解析并且放入数据库中,那就可以开一个IO通道,比如每读取1000m我们交给一个线程去处理。还有上面说到的注册,当我们把注册信息存储在数据库后就返回成果结果,后面的邮箱、vip、以及一系列操作交给线程去后台处理
  • 并行:多个线程共同工作,提升效率。
    java中如何使用线程
    继承thread类、实现Runnable接口、Callable/Future(此处不多余赘述,网上很多使用案例)

线程的原理

用一个很无聊的面试题来讲解,“为什么不直接调用run方法而是调用start方法”,让我们看下面的图:

  • 当调用run方法的时候,run方法去调用了jvm层间的方法
  • jvm判断你使用的系统类型(linux or windows or so on)然后去系统层间开辟线程
  • 系统层面进行cpu的调度算法告诉cpu
  • 当一个你的线程抢占到cpu的时候会回调jjvm,然后jvm去才去调用你的run方法

    线程的生命周期(引用网上的一个图)

除了start()开启一个线程->运行run()完线程自动销毁,还有其他状态(NEW \RUNNABLE \BLOCKED\ WAITING \TIMED_WAITING \TERMINATED)

阻塞状态(waiting):Thread#sleep()/wait()/join()

锁阻塞(blocked):synchronize同步锁

在这里插入图片描述
interrupt()进行线程的停止->(本质上把选择权利交给了开发者,这是一种安全的解决办法,因为有时候我们使用stop去结束一个线程,可能当前的线程并没有执行完成,突然中断可能造成事务不完整)

主动的停止方式:当run方法运行完成之后,线程停止

被动的方式:

public class InterruptDemo implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new InterruptDemo());
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //实际上在这里把选择权利交给了咱们,
                // 线程执行发现了需要进行中断,然后进入到catch中,复位线程状态为不中断状态,实际上就是改变‘while的控制状态’
                // 如果你想进行中断那就使用interrupt() 否则的话,线程将继续进行。
                // 因为你可能有一些线程中断后的操作,这个线程需要执行完成后在进行中断,那在catch中就可以进行操作
                Thread.currentThread().interrupt();
            }
            System.out.println("get information regularly");
        }
    }
}

排查线程问题

常见问题:cpu占用率很高:我们创建两个线程抢占资源来模拟

class ThreadRunA extends Thread {
    @Override
    public void run() {
        System.out.println("================A===================");
        synchronized (A.A) {
            System.out.println("begin to execute a。。。。" + Thread.currentThread().getName());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B.B) {
            }
            System.out.println("I've finished A。。。。" + Thread.currentThread().getName() + ":" + B.B.hashCode() + ":"
                    + A.A.hashCode());
        }
    }
}
class ThreadRunB extends Thread {
    @Override
    public void run() {
        System.out.println("================B===================");
        synchronized (B.B) {
            System.out.println("I am going to executeB。。。。" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (A.A) {
            }
            System.out.println("I am executing B。。。。" + Thread.currentThread().getName() + ":" + B.B + ":" + A.A);
        }
    }
}

cpu占用率不高但是相应很慢: 我们在创建一个死循环来模拟

class WhileThread
  implements Runnable
{
  public void run()
  {
    while (true)
      System.out.println("Thread");
  }
}

我们把这个打包成一个springBoot项目放在虚拟机上去运行

@RestController
public class ThreadController {

    @GetMapping("/loop")
    public String dumpWhile(){
        new Thread(new WhileThread()).start();
        return "ok";
    }

    @GetMapping("/dead")
    public String dumpDeadLock(){
        Thread a = new ThreadRunA();
        Thread b = new ThreadRunB();
        a.start();
        b.start();
        return "ok";
    }
}
class WhileThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("Thread");
        }
    }
}

nohup java -jar -Dserver.port=8088 thread-example-0.0.1-SNAPSHOT.jar > all.log &(对项目进行启动)

curl http://127.0.0.1:8088/dead (首先对死锁这个进行访问,我们发现cup占用并不是非常高,但是没有反应)

这里明确的告知是哪个线程发生了什么事情

curl http://127.0.0.1:8088/loop 执行这个来排查cpu占用率很高的问题(使用top命令,我们看到cpu已经快要满了)

然后执行 top -c 查询 占用cpu最高的进程 -> 拿到进程id去查询改进程中最消耗性能的线程 (top -H -p 90143)->拿到最消耗的性能的线程pid转化为二进制去

拿到二进制的pid去查询线程dump日志     

通过dump日志我们就能发现问题的所在   

对排查问题的方法做一个总结:

对于没有反应,但是cpu占用不高问题:

  • 首先jps去查询java进程的pid
  • 通过jstack jar前面的id 去查询线程日志

对于cpu占用很高:

  • top -c 找到占用资源最高的进程并获取id
  • top -H -p 进程pid 去查询该进程中最消耗的线程
  • printf "0x%x\n"线程pid 把线程pid 转化为二进制
  • jstack 最高占用率的进程id| grep -A 20二进制的最高占用率线程的pid

小结:

本章从总体对高并发到线程进行了一些说明,在后续章节会深入阐述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值