关于 Android 线程优化这些知识你都该了解

news2024/9/27 15:34:52

前言

在实际项目开发中会频繁的用到线程,线程使用起来是很简单,但是滥用线程会带来性能问题, 比如启动一个线程至少 占用16kb的内存、线程过多会导致cpu的频繁切换而cpu切换成本是很高的、消耗大量用户电量等问题, 所以应该让app的线程数保持在合理水平,这是性能优化中很重要的一部分。本文对线程优化方面的知识做了一个全面总结,主要内容如下:

一、线程调度原理解析

线程调度的原理

在任意时刻,CPU 只能执行一条机器指令,每个线程只有获得了 CPU 的使用权之后才能执行指令,也就是说 在任意时刻,只有一个线程占用 CPU,处于运行状态。而我们平常所说的 多线程并发运行,实际上说的是多个线程轮流获取 CPU 的使用权,然后分别执行各自的任务。其实在可运行池当中有多个处于就绪状态的线程在等待 CPU,而 JVM 负责线程调度,按照特定机制为多个线程分配 CPU 使用权

上面的描述提到了三个主要信息:

  • 在任意时刻,只有一个线程占用 CPU,处于运行状态

  • 多线程并发运行,实际上说的是多个线程轮流获取 CPU 的使用权

  • JVM 负责线程调度,按照特定机制为多个线程分配 CPU 使用权

线程调度模型

线程调度模型可以分为两类,分别是 分时调度模型抢占式调度模型

  • 分时调度模型:让所有线程轮流获取 CPU 的使用权,而且均分每个线程占用 CPU 的时间片,这种方式非常公平

  • 抢占式调度模型:JVM 使用的是抢占式调度模型,让优先级高的线程优先获取到 CPU 的使用权,如果在可运行池当中的线程优先级都一样,那就随机选取一个

Android 的线程调度

Android 的线程调度从两个因素决定,一个是 nice 值(即线程优先级),一个是 cgroup(即线程调度策略)。

对于 nice 值来说,它首先是在 Process 中定义的,值越小,进程优先级越高,默认值是 THREAD_PRIORITY_DEFAULT = 0,主线程的优先级也是这个值。修改 nice 值只需要在对应的线程下设置即可:

publicclassMyRunnableimplementsRunnable {<!-- -->
	@Override
	publicvoidrun() {<!-- -->
		Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT)
	}
}

// 附上 setThreadPriority() 文档说明/**
 * Set the priority of the calling thread, based on Linux priorities.  See
 * {@link #setThreadPriority(int, int)} for more information.
 * 
 * @param priority A Linux priority level, from -20 for highest scheduling
 * priority to 19 for lowest scheduling priority.
 * 
 * @throws IllegalArgumentException Throws IllegalArgumentException if
 * &lt;var&gt;tid&lt;/var&gt; does not exist.
 * @throws SecurityException Throws SecurityException if your process does
 * not have permission to modify the given thread, or to use the given
 * priority.
 * 
 * @see #setThreadPriority(int, int)
 */publicstaticfinalnativevoidsetThreadPriority(int priority)throws IllegalArgumentException, SecurityException;

复制代码

nice 值它还有其他的优先级可选:

publicclassProcess {
    /**
     * Standard priority of application threads.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */
	publicstaticfinalintTHREAD_PRIORITY_DEFAULT=0;
	
    /**
     * Lowest available thread priority.  Only for those who really, really
     * don't want to run if anything else is happening.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_LOWEST=19;	
    
    /**
     * Standard priority background threads.  This gives your thread a slightly
     * lower than normal priority, so that it will have less chance of impacting
     * the responsiveness of the user interface.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_BACKGROUND=10;    
    
    /**
     * Standard priority of threads that are currently running a user interface
     * that the user is interacting with.  Applications can not normally
     * change to this priority; the system will automatically adjust your
     * application threads as the user moves through the UI.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_FOREGROUND= -2;
    
    /**
     * Standard priority of system display threads, involved in updating
     * the user interface.  Applications can not
     * normally change to this priority.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_DISPLAY= -4;    
        
    /**
     * Standard priority of the most important display threads, for compositing
     * the screen and retrieving input events.  Applications can not normally
     * change to this priority.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_URGENT_DISPLAY= -8;

    /**
     * Standard priority of video threads.  Applications can not normally
     * change to this priority.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_VIDEO= -10;

    /**
     * Standard priority of audio threads.  Applications can not normally
     * change to this priority.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_AUDIO= -16;

    /**
     * Standard priority of the most important audio threads.
     * Applications can not normally change to this priority.
     * Use with {@link #setThreadPriority(int)} and
     * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
     * {@link java.lang.Thread} class.
     */publicstaticfinalintTHREAD_PRIORITY_URGENT_AUDIO= -19;

    /**
     * Minimum increment to make a priority more favorable.
     */publicstaticfinalintTHREAD_PRIORITY_MORE_FAVORABLE= -1;

    /**
     * Minimum increment to make a priority less favorable.
     */publicstaticfinalintTHREAD_PRIORITY_LESS_FAVORABLE= +1;    
}

复制代码

在实践过程当中,如果只有 nice 值是不足够的。比如有一个 app 它有1个前台线程,而且它还有10个后台线程,虽然后台线程的优先级比较低,但是数量比较多,这10个后台线程对 CPU 的消耗量是可以影响到前台线程的性能的。所以 Android 需要一种机制来处理这种情况,也就是 cgroup。

Android 借鉴了 Linux 的 cgroup 来执行 更严格的前台和后台调度策略,后台优先级的线程会被隐式的移动到后台 group,而其他 group 的线程如果处于工作状态,那么后台这些线程它们将会被限制,只有很小的几率能够利用 CPU。这种分离的调度策略既允许了后台线程来执行一些任务,同时又不会对用户可见的前台线程造成很大的影响,让前台线程有更多的 CPU

或许你会有疑问:哪些线程会被移到后台 group?

  • 第一种就是那些 手动设置了优先级比较低的线程

  • 第二种就是 不在前台运行的那些应用程序的线程

线程调度小结

  • 线程过多会导致 CPU 频繁切换,降低线程运行效率。 在前面讲解启动优化的时候有强调要充足的利用线程比如异步启动任务,但是线程也不能无限制的使用

  • 正确认识任务重要性决定哪种优先级。 一般情况下线程工作量和优先级是成反比,比如线程的工作量越大,所做的工作没那么重要,那这个线程的优先级应该越低

  • 线程的优先级具有继承性。 比如在 A 线程创建了 B 线程,在我们没有指定线程优先级的情况下,B 线程的优先级是和 A 一样的。所以我们在 UI 线程中创建线程,线程的优先级是和 UI 线程一样的,这就会导致 UI 线程抢占 CPU 时间片的概率会变少

二、Android 异步方式汇总

Thread

使用 Thread 创建线程是最简单、常见的异步方式,但在实际项目中,它也就只有这个优点了,并不推荐直接使用 Thread 创建线程,主要有以下几点原因:

  • 不易复用,频繁创建及销毁开销大

  • 复杂场景不易使用

HandlerThread

是 Android 提供的一个自带消息循环的线程,它内部使用 串行的方式执行任务,比较 适合长时间运行,不断从队列中获取任务的场景

IntentService

继承了 Android Service 组件,内部创建了 HandlerThread,相比 Service 是在主线程执行,IntentService 是 在子线程异步执行不占用主线程,而且 优先级比较高,不易被系统 kill

AsyncTask

AsyncTask 是 Android 提供的工具类,内部的实现是使用了线程池,它比较大的好处是无需自己处理线程切换,但需要注意 AsyncTask 不同版本执行方式不一致的问题。

线程池

java 提供了线程池,在实际项目中比较推荐使用线程池的方式实现异步任务,它主要有以下优点:

  • 易复用,减少线程频繁创建、销毁的时间

  • 功能强大:定时、任务队列、并发数控制等,java 提供了 Executors 工具类可以很方便的创建一个线程池,也可以自己定制线程池

RxJava

RxJava 由强大的 Scheduler 集合提供,内部实际也是使用的线程池,它封装的非常完善,可以根据任务类型的不同指定使用不同的线程池,比如 IO 密集型的任务可以指定 Schedulers.IO,CPU 密集型任务可以指定 Schedulers.Computation。

Single.just(xxx)
	.subscribeOn(Schedulers.IO) // 指定工作线程类型为 IO 密集型
	.observeOn(AndroidSchedulers.mainThread()) // 指定下游接收所在线程
	.subscribe();
复制代码

三、Android线程优化实战

线程使用准则

  • 严禁使用直接new Thread()的方式

  • 提供基础线程池供各个业务线使用: 避免各个业务线各自维护一套线程池,导致线程数过多

  • 根据任务类型选择合适的异步方式: 比如优先级低且长时间执行可以使用Handler Thread,再比如:有一个任务需要定时执行,使用线程池更适合

  • 创建线程必须命名: 方便定位线程归属于哪一个业务方,在线程运行期可以使用Thread.currentThread().setName修改名字

  • 关键异步任务监控: 异步不等于不耗时,如果一个任务在主线程需要耗费500ms,那么它在异步任务中至少需要500ms,因为异步任务中优先级较低,耗费时间很可能会高于500ms,所以这里可以使用AOP的方式来做监控,并且结合所在的业务场景,根据监控结果来适时的做一些相对应的调整

  • 重视优先级设置: 使用Process.setThreadPriority();设置,并且可以设置多次

线程池优化实战

接下来针对线程池的使用来做一个简单的实践,还是打开我们之前的项目,这里说一下每次实践的代码都是基于第一篇启动优化的那个案例上写的。

首先新建一个包async,然后在包中创建一个类ThreadPoolUtils,这里我们创建可重用且固定线程数的线程池,核心数为5,并且对外暴露一个get方法,然后我们可以在任何地方都能获取到这个全局的线程池:

publicclassThreadPoolUtils {

    //创建定长线程池,核心数为5privatestaticExecutorServicemService= Executors.newFixedThreadPool(5, newThreadFactory() {
        @Overridepublic Thread newThread(Runnable runnable) {
            Threadthread=newThread(runnable,"ThreadPoolUtils");//设置线程名
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //设置线程优先级return thread;
        }
    });

    //获取全局的线程池publicstatic ExecutorService getService(){
        return mService;
    }

}
复制代码

然后使用的时候就可以在你需要的地方直接调用了,并且你在使用的时候还可以修改线程的优先级以及线程名称:

//使用全局统一的线程池
        ThreadPoolUtils.getService().execute(newRunnable() {
            @Overridepublicvoidrun() {
                Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); //修改线程优先级StringoldName= Thread.currentThread().getName();
                Thread.currentThread().setName("Jarchie"); //修改线程名称
                Log.i("MainActivity","");
                Thread.currentThread().setName(oldName); //将原有名称改回去
            }
        });
复制代码

四、定位线程创建者

如何确定线程创建者

当你的项目做的越来越大的时候一般情况下线程都会变的非常多,最好是能够对整体的线程数进行收敛,那么问题来了,如何知道某个线程是在哪里创建的呢?不仅仅是你自己的项目源码,你依赖的第三方库、aar中都有线程的创建,如果单靠人眼review代码的方式,工作量很大而且你还不一定能找的全。并且你这次优化完了线程数,你还要考虑其他人新加的线程是否合理,所以就需要能够建立一套很好的监控预防手段。然后针对这些情况来做一个解决方案的总结分析,主要思想就是以下两点:

  • 创建线程的位置获取堆栈

  • 所有的异步方式,都会走到new Thread

解决方案:

  • 特别适合Hook手段

  • 找Hook点:构造函数或者特定方法

  • Thread的构造函数

可以在构造函数中加上自己的逻辑,获取当前的调用栈信息,拿到调用栈信息之后,就可以分析看出某个线程是否使用的是统一的线程池,也可以知道某个线程具体属于哪个业务方。

Epic实战

Epic简介

  • Epic是一个虚拟机层面、以Java Method为粒度的运行时Hook框架

  • 支持Android4.0-10.0(我的手机上程序出现了闪退,后来查找原因发现这个库开源版本一些高版本手机好像不支持)

Epic使用

  • implementation 'me.weishu:epic:0.6.0'

  • 继承XC_MethodHook,实现相应逻辑

  • 注入Hook:DexposedBridge.findAndHookMethod

代码中使用

@OverrideprotectedvoidattachBaseContext(Context base) {
        super.attachBaseContext(base);
        //Hook Thread类的构造函数,两个参数:需要Hook的类,MethodHook的回调
        DexposedBridge.hookAllConstructors(Thread.class, newXC_MethodHook() {
            //afterHookedMethod是Hook此方法之后给我们的回调@OverrideprotectedvoidafterHookedMethod(MethodHookParam param)throws Throwable {
                super.afterHookedMethod(param); //Hook完成之后会回调到这里//实现自己的逻辑,param.thisObject可以拿到线程对象Threadthread= (Thread) param.thisObject;
                //Log.getStackTraceString打印当前的调用栈信息
                Log.i(thread.getName() + "stack", Log.getStackTraceString(newThrowable()));
            }
        });
    }
复制代码

如果你的手机支持的话,这个时候运行程序应该就可以看到线程打印出来的堆栈信息了

五、优雅实现线程收敛

线程收敛常规方案

  • 根据线程创建堆栈考量合理性,使用统一线程库

  • 各业务线需要移除自己的线程库使用统一的线程库

基础库如何使用线程

  • 直接依赖线程库

  • 缺点:线程库更新可能会导致基础库也跟着更新

基础库优雅使用线程

  • 基础库内部暴露API:setExecutor

  • 初始化的时候注入统一的线程库

举个栗子: 比如这里有一个日志工具类,我们将它作为应用的日志基础库,假设它内部有一些异步操作,原始的情况下是它自己内部实现的,然后现在在它内部对外暴露一个API,如果外部注入了一个ExecutorService,那么我们就使用外部注入的这个,如果外部没有注入,那就使用它默认的,代码如下所示:

publicclassLogUtils {
    privatestatic ExecutorService mExecutorService;
 
    publicstaticvoidsetExecutor(ExecutorService executorService){
        mExecutorService = executorService;
    }
 
    publicstaticfinalStringTAG="Jarchie";
 
    publicstaticvoidi(String msg){
        if(Utils.isMainProcess(BaseApp.getApplication())){
            Log.i(TAG,msg);
        }
        // 异步操作if(mExecutorService != null){
            mExecutorService.execute(() -> {
                ...
            });
        }else {
            //使用原有的
            ...
        }
    }
}
复制代码

统一线程库

  • 区分任务类型:IO密集型、CPU密集型

  • IO密集型任务不消耗CPU,核心池可以很大(网络请求、IO读写等)

  • CPU密集型任务:核心池大小和CPU核心数相关(如果并发数超过核心数会导致CPU频繁切换,降低执行效率)

举个栗子:根据上面的说明,可以做如下的设置:

//获取CPU的核心数privateintCPUCOUNT= Runtime.getRuntime().availableProcessors();
 
    //cpu线程池,核心数大小需要和cpu核心数相关联,这里简单的将它们保持一致了privateThreadPoolExecutorcpuExecutor=newThreadPoolExecutor(CPUCOUNT, CPUCOUNT,
            30, TimeUnit.SECONDS, newLinkedBlockingDeque<>(), sThreadFactory);
 
    //IO线程池,核心数64,这个数量可以针对自身项目再确定privateThreadPoolExecutoriOExecutor=newThreadPoolExecutor(64, 64,
            30, TimeUnit.SECONDS, newLinkedBlockingDeque<>(), sThreadFactory);
 
    //这里面使用了一个count作为标记privatestaticfinalThreadFactorysThreadFactory=newThreadFactory() {
        privatefinalAtomicIntegermCount=newAtomicInteger(1);
        public Thread newThread(Runnable runnable) {
            returnnewThread(runnable, "ThreadPoolUtils #" + mCount.getAndIncrement());
        }
    };
复制代码

然后在你实际项目中需要区分具体的任务类型,针对性的选择相应的线程池进行使用。 以上就是对于Android线程优化方面的总结了,今天的内容还好不算多,觉得有用的朋友可以看看。

Android核心知识点笔记:

Android开发核心知识点笔记

Android Framework核心知识点笔记

音视频开发笔记,入门到高级进阶

Android Flutter核心知识点笔记与实战详解

性能调优核心知识点笔记

Android开发高频面试题,25个知识点整合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/376070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Set集合、HashSet集合、LinkedHashSet集合

1、Set集合的特点 无序&#xff0c;不重复、无索引 Set集合的方法上基本和Collection的API一致 2、Set集合的实现类特点 HashSet&#xff1a;无序、不重复、无索引 LinkedHashList&#xff1a;有序、不重复、无索引 TreeSet&#xff1a;可排序、不重复、无索引 public s…

taobao.fulfillment.order.assemble( 拆合单结果回传接口 )

&#xffe5;免费必须用户授权 拆合单结果回传接口 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, secr…

centos7合并home分区到root分区

最近在尝试通过物理机安装 CentOS&#xff0c;官方镜像默认安装时&#xff0c;如果没有手动分区&#xff0c;默认设置是会将 home 单独分区&#xff0c;系统分区默认为 50 GB&#xff0c;这里提供方法将 home 分区合并到 root 分区。 1.查看当前系统分区情况 输入命令&#x…

华为OD机试用Python实现 -【删除重复数字后的最大数字】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲删除重复数字后的最大数字题目输入输出示例一输入输出示例二输入输出Python 代码展示编码思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.ne…

RedisTemplate和StringRedisTemplate的区别

RedisTemplate和StringRedisTemplate的区别&#xff1a; 两者的关系是StringRedisTemplate继承RedisTemplate。两者的数据是不共通的&#xff1b;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据&#xff0c;RedisTemplate只能管理RedisTemplate中的数据。…

图解鼠标事件的 ScreenX ,LayerX,clientX,PageX,offsetX,X

前言&#xff1a; 完在上一篇文章 &#x1f381;如何实现原生 JS 的拖拽效果我中使用到了 MouseEvent 事件对象身上的 clienX 的属性&#xff0c;但同时我也注意到了事件对象身上关于 X 的相关属性还有很多&#xff0c;并且在移动端开发中&#xff0c;这些属性需要频繁的用到&a…

golang 实现链表爽不爽?

犹记得刚学 C 语言的时候&#xff0c;学到指针这一章&#xff0c;就会有让我们写链表的需求&#xff0c;头插法&#xff0c;尾插法&#xff0c;翻转链表&#xff0c;合并链表&#xff0c;约瑟夫环等等 学的不亦乐乎&#xff0c;但是 对于指针刚学的时候&#xff0c;真是摸不着…

DolphinDB实现动量交易策略详解

动量策略是最流行的量化策略之一。商品期货的CTA策略&#xff0c;绝大多数都是基于动量策略。在股票市场&#xff0c;动量策略也是常用的量化因子之一。通俗地讲&#xff0c;动量策略就是“追涨杀跌”。下面我们将介绍如何在DolphinDB中测试动量交易策略&#xff0c;并计算动量…

第三阶段04-同步请求和异步请求,get/post,Josn,pojo,Session/Cookie,过滤器Filter

文章目录同步请求和异步请求客户端如何发出异步请求自定义模板代码Get和Post请求异步版本的注册和登录商品管理系统(异步版本)商品列表步骤:前后端分离为什么需要前后端分离?为什么以后不再使用同步请求?JSONPOJO会话对象Session如何记住登录状态后端的MVC会话管理Cookie通过…

交叉编译 iceoryx

交叉编译 iceoryx 概述 iceoryx 是用于各种操作系统的进程间通信&#xff08;IPC&#xff09;中间件&#xff08;目前支持 Linux、macOS、QNX、FreeBSD 和 Windows 10&#xff09;。它起源于汽车行业&#xff0c;当涉及到驾驶员辅助或自动驾驶系统时&#xff0c;需要在不同的过…

一起来学 next.js - getStaticProps、getStaticPaths 篇

之前讲过 next.js 中的 getServerSideProps&#xff0c;今天来讲一讲另一个很类似的 API&#xff1a;getStaticProps&#xff0c;以及和 getStaticProps 紧密相关的 getStaticPaths。 getStaticProps 主要用于构建时落地一些静态数据&#xff0c;但不同于 getServerSideProps&…

动态规划背包问题

背包问题的分类 拿到背包问题,最重要的是会归类到哪一种背包问题中,常见的考题里主要是01背包和完全背包,leetcode上连多重背包的题目都没有。实际完全背包问题就是01背包的一种。 对一和零这道题,很多人容易把m看成一个背包,n看成另一个背包,从而当做多重背包。然而这…

ChatGPT创业潮来了,我既兴奋又焦虑

最近一段时间&#xff0c;ChatGPT从AI领域破圈&#xff0c;成了互联网领域的新风口。创业者和投资人也兴奋起来了。 创业的方向已经很明了。做中国版对标ChatGPT的大模型&#xff0c;是显而易见的一条路。目前百度的“文心一言”已经呼之欲出&#xff0c;原美团联合创始人王慧…

超店有数分享:2023还有哪些tiktok数据值得关注?

目前&#xff0c;tiktok是全球增长最迅猛的社交媒体软件之一。很多商家瞄准了tiktok的变现转化潜力&#xff0c;纷纷入局tiktok电商赛道。在入局这个赛道之前&#xff0c;我们需要了解一些tiktok的相关数据&#xff0c;这样才能更好的了解大局&#xff0c;及时调整自己的业务情…

Swagger2基本使用

这里写目录标题废话篇前言什么是openAPISwagger简介Springfox使用篇swagger的基本使用废话篇 前言 接口文档对于前后端开发人员都十分重要。尤其近几年流行前后端分离后接口文档又变成重中之重。接口文档固然重要&#xff0c;但是由于项目周期等原因后端人员经常出现无法及时…

P21.为什么需要父子shell

脚本的不同执行方式是否会开启子shell 父Shell概念 &#xff08;1&#xff09;pstree查看父进程 如下命令安装pstree yum install -y psmiscsshd--sshd--bash--pstree 第一个sshd为操作系统自己启动的监听sshd服务的程序第二个sshd是shell连接过来启动的shell每次调用bash…

echarts scatter地图上渲染圆点

目录 实现效果实现步骤相关文件实现效果 如上图所示,我们经常遇到一些需求,在地图上根据数据的大小,再画一些直径不一的实心圆。 首先,我们得先把地图画好,再来画这些跟数据有关联的圆形。 为了方便scatter建立坐标系,我们采用geo来自定义绘制地图,而不是使用百度地图或…

前端面试题 —— 计算机网络(二)

目录 一、POST和PUT请求的区别 二、GET方法URL长度限制的原因 三、页面有多张图片&#xff0c;HTTP是怎样的加载表现&#xff1f; 四、HTTP2的头部压缩算法是怎样的&#xff1f; 五、说一下HTTP 3.0 六、HTTP协议的性能怎么样&#xff1f; 七、数字证书是什么&#xff1f…

DC-3 靶场学习

文章目录环境配置&#xff1a;信息搜集&#xff1a;漏洞利用&#xff1a;获取FLAG&#xff1a;环境配置&#xff1a; 还是让DC-3的靶场和kali处于同于网段即可。 DC-3靶场还需要修改一下CD/DVD设置&#xff08;如下&#xff09;。 信息搜集&#xff1a; 首先还是要获取靶机的…

市场上有很多低代码开发平台,不懂编程的人可以用哪些?

市场上有很多低代码开发平台&#xff0c;不懂编程的人可以用哪些&#xff1f;这个问题一看就是外行问的啦&#xff0c;低代码平台主打的就是一个“全民开发”&#xff0c;而且现在很多低代码平台都发展为零代码了&#xff0c;不懂编程也完全可以使用&#xff01; 所谓低代码开…