Android Framework-Android进程/线程和程序内存优化

news2024/9/28 23:33:53

Android进程和线程

进程(Process)是程序的一个运行实例,以区别于“程序”这一静态的概念;而线程(Thread)则是CPU调度的基本单位。
Android中的程序和进程具体是什么概念呢?
一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。而main()函数的工作也是千篇一律的。总结如下:
初始化
比如Windows环境下通常要创建窗口、向系统申请资源等。
进入死循环
并在循环中处理各种事件,直到进程退出。
这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。
以AndroidManifest.xml为例:

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com. 
android.launchperf">
<application android:label="Launch Performance">
 <activity android:name="SimpleActivity" android:label="Simple
Activity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
 </activity>

可以看到,Activity的外围有一个名为< application>的标签。换句话说,四大组件只是“application”的“零件”。
自动创建一个activity,然后在onCreate方法打个断点,
在这里插入图片描述
那么当这个Activity启动后,将会生成几个Thread呢(是不是只会有一个主线程)?如图所示。
在这里插入图片描述
一个由向导诞生的、没有任何实际功能的Activity程序,有三个线程,除了有我们最熟悉的main thread(即图中的Thread[<1> main])外,还有两个Binder Thread。主线程由ZygoteInit启动,经由一系列调用后最终才执行Activity本身的onCreate()函数。Zygote为Activity创建的主线程是ActivityThread:

 SamplingProfilerIntegration.start();
 CloseGuard.setEnabled(false);
 Process.setArgV0("<pre-initialized>");
 Looper.prepareMainLooper(); /*只有主线程才能调用这个函数,普通线程应该使用prepare(),
 */
 if (sMainThreadHandler == null) {
 sMainThreadHandler = new Handler(); /*主线程对应的Handler*/
 }
 ActivityThread thread = new ActivityThread(); /*这个main()是static的,因此在这里需要创建一个实例*/
 thread.attach(false); /*Activity是有界面显示的,这个函数将与WindowManagerService
 建立联系。*/Looper.loop(); /*主循环开始*/
 throw new RuntimeException("Main thread loop
unexpectedly exited");/*如果程序运 行到这里,说明退出了上面的Looper循环*/
 }

Service也是寄存于ActivityThread之中的,并且启动流程和Activity基本一致。
启动Service时,也同样需要两个Binder线程的支持。

对于同一个AndroidManifest.xml中定义的四大组件,除非有特别声明(,否则它们都运行于同一个进程中(并且均由主线程来处理事件)。
结论:
1.四大组件并不是程序(进程)的全部,而只是它的“零件”。
2.应用程序启动后,将创建ActivityThread主线程。
3.同一个包中的组件将运行在相同的进程空间中。
4.不同包中的组件可以通过一定的方式运行在一个进程空间中。
5.一个Activity应用启动后至少会有3个线程:即一个主线程和两个Binder线程。

Handler, MessageQueue, Runnable与Looper

概念初探
在这里插入图片描述
Runnable,Message,MessageQueue,Looper和Handler的关系简图
Runnable和Message可以被压入某个MessageQueue中,形成一个集合
在这里插入图片描述
注意,一般情况下某种类型的MessageQueue只允许保存相同类型的Object。图中我们只是为了叙述方便才将它们混放在同一个MessageQueue中,实际源码中需要先对Runnalbe进行相应转换。
Looper循环地去做某件事
比如在这个例子中,它不断地从MessageQueue中取出一个item,然后传给Handler进行处理,如此循环往复。假如队列为空,那么它会进入休眠。
Handler是真正“处理事情”的地方
它利用自身的处理机制,对传入的各种Object进行相应的处理并产生最终结果。
用一句话来概括它们,就是:
Looper不断获取MessageQueue中的一个Message,然后由Handler来处理。
它们各司其职,很像一台计算机中CPU的工作方式::中央处理器(Looper)不断地从内存
(MessageQueue)中读取指令(Message),执行指令(Handler),最终产生结果。
Thread和Handler的关系
在这里插入图片描述
① 每个Thread只对应一个Looper;
② 每个Looper只对应一个MessageQueue;
③ 每个MessageQueue中有N个Message;
④ 每个Message中最多指定一个Handler来处理事件。
由此可以推断出,Thread和Handler是一对多的关系。
Handler的作用:
1.处理Message,这是它作为“处理者”的本职所在。
2.将某个Message压入MessageQueue中。
实现第一个功能的相应函数声明如下:

public void dispatchMessage(Message msg);//对Message进行分发
public void handleMessage(Message msg);//对Message进行处理

Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略来将Message分发给相应的责任人
Handler的第二个功能,相应的功能函数声明如下:

1Post系列:
final boolean post(Runnable r);
final boolean postAtTime(Runnable r, long uptimeMillis);2Send系列:
final boolean sendEmptyMessage(int what);
final boolean sendMessageAtFrontOfQueue(Message msg);
boolean sendMessageAtTime(Message msg, long uptimeMillis);
final boolean sendMessageDelayed(Message msg, long
delayMillis);等等

Post和Send两个系列的共同点是它们都负责将某个消息压入MessageQueue中;区别在于后者处理的函数参数直接是Message,而Post则需要先把其他类型的“零散”信息转换成Message,再调用Send系列函数来执行下一步。
更贴近Android实现的“印象图”
在这里插入图片描述
Looper中包含了一个MessageQueue。下面是一个使用Looper的普通线程范例,名为LooperThread:

class LooperThread extends Thread {
 public Handler mHandler;
 public void run() {
 Looper.prepare();/*一句简单的prepare,究竟做了些什么工作?
*/
 mHandler = new Handler() {
 public void handleMessage(Message msg) {/*处理消息的地方。继承Handler的子类通常需要修改这个函数
*/
 }
 };
 Looper.loop();/*进入主循环*/
 }
 }

这段代码在Android线程中很有典型意义。概括起来只有3个步骤
(1)Looper的准备工作(prepare);
(2)创建处理消息的handler;
(3)Looper开始运作(loop)。
分析一下上边的代码:
首先是:

Looper.prepare();

既然要使用Looper类的函数,那么LooperThread中肯定就得执行如下操作:

import android.os.Looper;

仔细观察,Looper里有个非常重要的成员变量:

static final ThreadLocal<Looper> sThreadLocal = new
ThreadLocal<Looper>();

这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThreadLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即便是同一进程)一概无法访问到它。这从侧面告诉我们,每个线程的Looper都是独立的。

prepare:

private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {/*sThreadLocal.get返回的
是模板类,这个场景中是Looper。
 这个判断保证一个Thread只会有
一个Looper实例存在*/
 throw new RuntimeException("Only one Looper may be
created per thread");
 }
 sThreadLocal.set(new
Looper(quitAllowed));
 }

sThreadLocal的确保存了一个新创建的Looper对象。

接下来创建一个Handler对象,我们单独将它提取出来以方便阅读。

public Handler mHandler;
…
mHandler = new Handler() {
 public void handleMessage(Message msg) {}
};

可见,mHandler是LooperThread的成员变量,并通过new操作创建了一个Handler实例。
Handler有多个构造函数,比如:

public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);

之所以有这么多构造函数,是因为Handler有如下内部变量需要初始化:

final MessageQueue mQueue;
 final Looper mLooper;
 final Callback mCallback;

我们就以LooperThread例子中采用的第一个函数来讲解下它的构造函数:

public Handler() {
 /*…省略部分代码*/
 mLooper =Looper.myLooper();/*还是通过sThreadLocal.get来获取当
前线程中的Looper实例*/
 …
 mQueue= mLooper.mQueue; /*mQueue是Looper与
Handler之间沟通的桥梁*/
 mCallback = null;
}

这样Handler和Looper,MessageQueue就联系起来了

UI主线程——ActivityThread

public static void main(String[] args) {Looper.prepareMainLooper();//和前面的LooperThread不同
 ActivityThread thread = new ActivityThread();//新建一个
ActivityThread对象
 thread.attach(false);
 if (sMainThreadHandler == null) {
 sMainThreadHandler = thread.getHandler();//主Handler
 }
 AsyncTask.init();
 Looper.loop();
 throw new RuntimeException("Main thread loop
unexpectedly exited");
 }

prepareMainLooper和prepare
普通线程只要prepare就可以了,而主线程使用的是prepareMainLooper。
普通线程生成一个与Looper绑定的Handler对象就行,而主线程是从当前线程中获取的Handler(thread.getHandler)
(1)prepareMainLooper

public static void prepareMainLooper() {
 prepare(false);//先调用prepare
 synchronized (Looper.class) {
 if (sMainLooper != null) {
 throw new IllegalStateException("The main Looper
has already been prepared.");
 }
 sMainLooper = myLooper();
 }
 }

可以看到,prepareMainLooper也需要用到prepare。参数false表示该线程不允许退出,这和前面的LooperThread不一样。经过prepare后,myLooper就可以得到一个本地线程的Looper对象,然后将其赋给sMainLooper。从这个角度来讲,主线程的sMainLooper其实和其他线程的Looper对象并没有本质的区别
在这里插入图片描述
这个图描述的是一个进程和它内部两个线程的Looper情况,其中线程1是主线程,线程2是普通线程。方框表示它们能访问的范围,如线程1就不能直接访问到线程2中的Looper对象,但二者都可以接触到进程中的各元素。
线程1:因为是Main Thread,它使用的是prepareMainLooper(),这个函数将通过prepare()为线程1生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样做的目的就是其他线程如果要获取主线程的Looper,只需调用getMainLooper()即可。
线程2:作为普通线程,它调用的是prepare();同时也生成了一个ThreadLocal的Looper对象,只不过这个对象只能在线程内通过myLooper()访问。当然,主线程内部也可以通过这个函数访问它的Looper对象。
(2)sMainThreadHandler。
当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象:

final H mH = new H();

ActivityThread.main中调用的thread.getHandler()返回的就是mH。
也就是说,ActivityThread提供了一个“事件管家”,以处理主线程中的各种消息。
Looper.loop

public static void loop() {
 final Looper me = myLooper();/*loop函数也是静态的,所以它只能访问静态数据。函数myLooper 则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实 就是取出之前prepare中创建的那个Looper对象)*/final MessageQueue queue = me.mQueue;/*正如我们之前所说,Looper中自带一个MessageQueue*/for (;;) {//消息循环开始
 Message msg = queue.next();/*从MessageQueue中取出一个消息,可能会阻塞*/
 if (msg == null) {/*如果当前消息队列中没有msg,说明线程要退出了。类比于上面Windows 伪代码例子中的while判断条件为0,这样就会结束循环*/
 return; /*直接返回,结束程序*/
 }
 …
 msg.target.dispatchMessage(msg); /*终于开始分派消息了,重心就在这里。变量target其实是一个Handler,所以dispatchMessage最终调用的是Handler中的处理函数。*/
 …
 msg.recycle();/*消息处理完毕,进行回收*/
 }
 }

loop()函数的主要工作就是不断地从消息队列中取出需要处理的事件,然后分发给相应的责任人。如果消息队列为空,它很可能会进入睡眠以让出CPU资源。而在具体事件的处理过程中,程序会post新的事件到队列中。另外,其他进程也可能投递新的事件到这个队列中。APK应用程序就是不停地执行“处理队列事件”的工作,直到它退出运行,如图所示。
在这里插入图片描述
在这个模型图中,ActivityThread这个主线程从消息队列中取出Message后,调用它对应的Runnable.run进行具体的事件处理。在处理的过程中,很可能还会涉及一系列其他类的调用(在图中用Object1,
Object2表示)。而且它们可能还会向主循环投递新的事件来安排后续操作。另外,其他进程也同样可以通过IPC机制向这一进程的主循环发送新事件,如触摸事件、按键事件等。这就是APK应用程序能“动起
来”的根本原因。
MessageQueue

/*以下代码还是Looper.java中的,不过只提取出MessageQueue相关的部分
*/
 final MessageQueue mQueue; /*注意它不是static的*/
 private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed); /*new了一个MessageQueue,就是它了。也就是说,当Looper创建时,消息队列也同时会被创建出来*/
 mRun = true;
 mThread = Thread.currentThread();//Looper与当前线程建立对应关系
 }

事实证明Looper内部的确管理了一个MessageQueue,它将作为线程的消息存储仓库,配合Handler、Looper一起完成一系列操作。

Thread类

Thread类的内部原理
 public class Thread implements Runnable {

可以看到,Thread实现了Runnable,也就是说线程是“可执行的代码”:

public interface Runnable {
 public void run();
}

Runnable是一个抽象接口类,唯一提供的方法就是run()。一般情况下,我们是这样使用Thread的:
方法1,继承自Thread
定义一个MyThread继承自Thread,重写它的run方法,然后调用:

MyThread thr = new MyThread();
thr.start();

方法2,直接实现Runnable
Thread的关键就是Runnable,因而下面是另一个常见用法:

new Thread(Runnable target).start();

这两种方法最终都通过start启动,它会间接调用上面的run实现。

 checkNotStarted();
 hasBeenStarted = true;
 VMThread.create(this, stackSize);/*这里是真正创
建一个CPU线程的地方*/
}

在此之前,我们一直都运行在“老线程”中,直到VMThread.create——而实际上真正在新线程中运行的只有Run方法,这解释了上面第二种方法通过传入一个Runnable也可以奏效的原因。从这个角度来理解,Thread类只能算是一个中介,任务就是启动一个线程来运行用户指定的Runnable,而不管这个Runnable是否属于自身,如图5-13所示。
在这里插入图片描述
线程有如下几种状态:
public enum State {
NEW, //线程已经创建,但还没有start
RUNNABLE, //处于可运行状态,一切就绪
BLOCKED, //处于阻塞状态,比如等待某个锁的释放
WAITING, //处于等待状态
TIMED_WAITING, //等待特定的时间
TERMINATED //终止运行
}

Thread休眠和唤醒

1.wait和notify/notifyAll
这3个函数是由Object类定义的——也就意味着它们是任何类的共有“属性”
在这里插入图片描述
当某个线程(比如SystemServer所在线程)调用一个Object(比如BlockingRunnable)的wait方法时,系统就要在这个Object中记录这个请求。因为调用者很可能不止一个,所以可使用列表(见图5-15的
waiting list)的形式来逐一添加它们。当后期唤醒条件(也就是BlockingRunnable执行了run后)满足时,Object既可以使用notify来唤醒列表中的一个等待线程,也可以通过notifyAll来唤醒列表中的所
有线程,如图5-15所示
在这里插入图片描述
2.interrupt
调用一个线程的interrupt的目的和这个单词的字面意思一样,就是“中断”它的执行过程。此时有以下3种可能性。
1.如果Thread正被blocked在某个object的wait上,或者join(),sleep()方法中,那么会被唤醒,中断状态会被清除并接收到InterruptedException。
2.如果Thread被blocked在InterruptibleChannel的I/O操作中,那么中断状态会被置位,并接收到ClosedByInterruptException,此时channel会被关闭。
3.如果Thread被blocked在Selector中,那么中断状态会被置位并且马上返回,不会收到任何exception。
3.join
join方法有如下几个原型:

public final void join ();
public final void join (long millis, int nanos);
public final void join (long millis);

比如:

Thread t1 = new Thread(new ThreadA()); 
Thread t2 = new Thread(new ThreadB()); 
t1.start(); 
t1.join(); 
t2.start();

它希望达到的目的就是只有当t1线程执行完成时,我们才接着执行后面的t2.start()。这样就保证了两个线程的顺序执行。而带有时间参数的join()则多了一个限制,即假如在规定时间内t1没有执行完成,那么我们也会继续执行后面的语句,以防止“无限等待”拖垮整个程序。
4.sleep
wait是等待某个object,而sleep则是等待时间,一旦设置的时间到了就会被唤醒。

Thread状态迁移
在这里插入图片描述
Thread+Handler+Looper的组合实例
BusinessThread

private Thread mBusinessThread = null;
private boolean mBusinessThreadStarted = false;
private BusinessThreadHandler mBusinessThreadHandler = null; 
private void startBusinessThread()
{
 if (true == mBusinessThreadStarted)
 return;
 else
 mBusinessThreadStarted = true;
 mBusinessThread = new Thread(new Runnable()
 {
 @Override
 public void run()
 {
 Looper.prepare();
 mBusinessThreadHandler = new
BusinessThreadHandler();
 Looper.loop();
 }
 });
 mBusinessThread.start();
}

BusinessThread重写了run方法,并使用Looper.prepare和Looper.loop来不断处理调整请求。这些请求是通过mBusinessThreadHandler发送到BusinessThread的消息队列中的。

public class BusinessThreadHandler extendsHandler
{
 public boolean sendMessage(int what, int arg1, int arg2)//重写sendMessage
 {
 removeMessages(what); //清理消息队列中未处理的请求
 return super.sendMessage(obtainMessage(what, arg1,arg2));//发送消息到队列
 }
 public void handleMessage(Message msg)
 {
 switch(msg.what)
 {
 case MSG_CODE:
 //在这里执行耗时操作
 break;
 default:
 break;
 }
 }
};

Android应用程序如何利用CPU的多核处理能力

开发人员如何主动去利用CPU的多核能力,从而有效提高自身应用程序的性能呢?
答案就是针对Java-Based的并行编程技术。
第一种方式就是Java线程,它在Android系统中同样适用。使用上也和Java没有太多区别,我们只要继承Thread类或者实现Runnable接口就可以了。不过采用这类方法有一点比较麻烦的地方,就是和主线程的通信需要通过Message Queue——因为只有主线程才能处理UI相关的事务,包括UI界面更新
另一种可选的并行编程方法是AsyncTask,它是Android开发的专门用于简化多线程实现的Helper类型的类。优点很明显,就是可以不需要通过繁琐的Looper、Handler等机制来与UI线程通信。
AsyncTask在设计时的目标是针对比较短时间的后台操作,换句话说,如果你需要在后台长时间执行某些事务的话,我们建议你还是使用java.util.concurrent包所提供的Executor、ThreadPoolExecutor和FutureTask等其它API接口。
第三种比较常用的“工作线程”实现模型是IntentService。

Android应用程序的典型启动流程

APK类型的应用程,它们通常由两种方式在系统中被启动
1.在Launcher中点击相应的应用程序图标启动
这种启动方式大多是由用户发起的。默认情况下APK应用程序在Launcher主界面上会有一个图标,通过点击它可以启动这个应用程序指定的一个Activity。
2.通过startActivity启动
这种启动方式通常存在于源码内部。比如在Activity1中通过startActivity来启动Activity2。
这两种启动方式的流程基本上是一致的,最终都会调用ActivityManagerService的startActivity来完成。

在这里插入图片描述
无论以什么方式发起一个Activity的启动流程,最终都会调用到AMS的startActivity函数。

如果一切顺利,AMS才会最终尝试启动指定的Activity。如果读者写过APK应用程序,应该清楚Activity的生命周期中除了onCreate,onResume外,还有onPause,onStop等。其中的onPause就是在此时被
调用的——因为Android系统规定,在新的Activity启动前,原先处于resumed状态的Activity会被pause。这种管理方式相比于Windows的多窗口系统简单很多,同时也完全可以满足移动设备的一般需求。将一
个Activity置为pause主要是通过此Activity所属进程的ApplicationThread.schedulePauseActivity方法来完成的。ApplicationThread是应用程序进程提供给AMS的一个Binder通道。

当收到pause请求后,此进程的ActivityThread主线程将会做进一步处理。除了我们熟悉的调用Activity.onPause()等方法外,它还需要通知WindowManagerService这一变化——因为用户界面也需要发生改变。做完这些以后,进程通知AMS它的pause请求已经执行完成,从而使得AMS可以接着完成之前的startActivity操作。

假如即将启动的Activity所属的进程并不存在,那么AMS还需要先把它启动起来。这一步由Process.start实现,它的第一个入参是“android.app.ActivityThread”,也就是应用程序的主线程,然后调用它的main函数。

ActivityThread启动并做好相应初始化工作后,需要调用attachApplication来通知AMS,后者才能继续执行未完成的startActivity流程。具体而言,AMS通过ApplicationThread.scheduleLaunchActivity请求应用程序来启动一个指定的Activity。之后的一系列工作就要依靠应用进程自己来完成,如Activity创建
Window,ViewRootImpl,遍历View Tree等。

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

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

相关文章

十一、Vben框架部分组件样式的重新封装

在使用vben框架的时候发现部分的样式不符合实际的需求&#xff0c;ant-design-vue的样式也不支持我们的需求&#xff0c;那怎么办呢&#xff0c;只能自己来修改&#xff0c;下面我就改大家说说我遇到的一些修改过的样式和组件。 一、inputNumber带前后标志 先看下目前支持的样…

命令查看Linux服务器内存、CPU、显卡、硬盘使用情况

命令查看Linux服务器内存、CPU、显卡、硬盘使用情况 查看内存使用情况 使用命令&#xff1a;free -m 大致结果类似下图&#xff1a; 内存占用情况 参数解释&#xff1a; Mem行&#xff08;单位均为M&#xff09;&#xff1a; total&#xff1a;内存总数used&#xff1a;已…

4.4 like通配符关键字过滤查询数据

文章目录1.概述2.LIKE关键字3.百分号&#xff08;%&#xff09;通配符3.1 单个百分号&#xff08;%&#xff09;通配符3.2 多个百分号&#xff08;%&#xff09;通配符3.3 在值的中间使用百分号&#xff08;%&#xff09;通配符3.4 注意事项4.下划线&#xff08;_&#xff09;通…

centos7 配置samba

samba概述&#xff1a; Windows与Linux之间通信的桥梁&#xff0c;Samba是一个非常强大的文件服务器。Samba端口&#xff1a;udp 137 udp138&#xff0c;tcp139 tcp445。Samba工作模式&#xff1a;C/S模式&#xff08;客户端-服务器&#xff09; samba应用环境 1、文件共享&…

python库--urllib

目录 一.urllib导入 二.urllib爬取网页 三.Headers属性 1.使用build_opener()修改报头 2.使用add_header()添加报头 四.超时设置 五.get和post请求 1.get请求 2.post请求 urllib库和request库作用差不多&#xff0c;但比较起来request库更加容易上手&#xff0c;但该了…

SpringCloud学习笔记 - 分布式系统全局事务管理 - Seata1.5.2+Nacos+OpenFeign

1. Seata 是什么? 由于业务和技术的发展&#xff0c;单体应用被拆分成微服务应用&#xff0c;原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源&#xff0c;业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证&#xff0c; 但是全…

【跟着ChatGPT学深度学习】ChatGPT带我学情感分析

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

5G+车联网按下加速键,这家企业已经走在行业前列

进入2023年&#xff0c;5G车联网进入了快速增长阶段&#xff0c;并且正在逐步替代4G的存量市场。 为了更好地满足5G车联网市场的需求&#xff0c;移远通信正式推出了符合3GPP Release 16标准的车规级5G NR模组AG59x系列。据了解&#xff0c;全新的产品在5G传输速度、低时延、高…

SpringBoot addResourceHandlers 代理静态资源无法访问 Java获取linux文件中文名乱码 Linux设置中文字符集

SpringBoot addResourceHandlers 代理静态资源无法访问 Java获取linux文件中文名乱码 linux设置中文字符集Windows中使用SpringBoot addResourceHandlers代理静态资源访问Linux中使用SpringBoot addResourceHandlers代理静态资源访问修改路径问题一度以为Linux不能用这种方式代…

概念解读稳定性保障

什么是稳定百度百科关于稳定的定义&#xff1a;“稳恒固定&#xff1b;没有变动。”很明显这里的“稳定”是相对的&#xff0c;通常会有参照物&#xff0c;例如 A 车和 B 车保持相同速度同方向行驶&#xff0c;达到相对平衡相对稳定的状态。那么软件质量的稳定是指什么呢&#…

区块链对于底层技术的助力和改造,导致了如此多的新技术、新模式的出现

现在&#xff0c;区块链就在经历这样一种状态。是的&#xff0c;我们现在看到的是&#xff0c;以人工智能为代表的诸多新技术的不断地成熟和落地&#xff0c;我们现在看到的是&#xff0c;以元宇宙为代表的诸多新模式的不断衍生和出现。但是&#xff0c;如果深度分析&#xff0…

对称锥规划:锥与对称锥

文章目录对称锥规划&#xff1a;锥与对称锥锥的几何形状常用的指向锥Nonnegative Orthant二阶锥半定锥对称锥对称锥的平方操作对称锥的谱分解对称锥的自身对偶性二阶锥规划SOCP参考文献对称锥规划&#xff1a;锥与对称锥 本文主要讲锥与对称锥的一些基本概念。 基础预备&…

C++回顾(四)—— 类的封装

4.1 面向对象编程介绍 4.1.1 什么是面向对象 面向将系统看成通过交互作用来完成特定功能的对象的集合。每个对象用自己的方法来管理数据。也就是说只有对象内部的代码能够操作对象内部的数据。 4.1.2 面向对象的特点 &#xff08;1&#xff09;抽象的作用 抽象是人们认识事…

【XXL-JOB】XXL-JOB的搭建和使用

【XXL-JOB】XXL-JOB的搭建和使用 文章目录【XXL-JOB】XXL-JOB的搭建和使用1. 任务调度1.1 实现任务调度1.1.1 多线程实现1.1.2 Timer实现1.1.3 ScheduledExecutor实现2. 分布式任务调度2.1 采用分布式的原因3. XXL-JOB3.1 XXL-JOB介绍3.2 执行流程4. 搭建XXL-JOB4.1 创建数据库…

Ep_操作系统面试题-操作系统的分类

答案 单体系统 整个操作系统是以程序集合来编写的&#xff0c;链接在一块形成一个二进制可执行程序&#xff0c;这种系统称为单体系统。 分层系统 每一层都使用下面的层来执行其功能。 微内核 微内核架构的内核只保留最基本的能力&#xff0c;把一些应用放到了用户空间 客户-…

BCN点击试剂1263166-90-0,endo BCN-OH,环丙烷环辛炔羟基

endo BCN-OH基础产品数据&#xff1a;CAS号&#xff1a;1263166-90-0中文名&#xff1a;环丙烷环辛炔甲醇&#xff0c;环丙烷环辛炔羟基英文名&#xff1a;endo BCN-OH 结构式&#xff08;Structural&#xff09;&#xff1a;详细产品数据&#xff1a;Molecular formula&#x…

CVPR 2023 | 基础模型推动语义分割的弱增量学习

前言语义分割的弱增量学习&#xff08;WILSS&#xff09;目的是学习从廉价和广泛可用的图像级标签中分割出新的类别&#xff0c;但图像级别的标签不能提供定位每个片段的细节。为了解决该问题&#xff0c;本文提出了一个新颖且数据高效的框架&#xff08;FMWISS&#xff09;。该…

IM即时通讯开发实时消息的“时序性”与“一致性”

我们都知道&#xff0c;一个典型的分布式系统中&#xff0c;很多业务场景都需要考虑消息投递的时序&#xff0c;例如&#xff1a;IM中单聊消息投递&#xff1a;保证发送方发送顺序与接收方展现顺序一致&#xff1b;IM中群聊消息投递&#xff1a;保证所有接收方展现顺序一致&…

如何审计一个智能合约

智能合约审计用于整个 DeFi 生态系统&#xff0c;通过对协议代码的深入审查&#xff0c;可以帮助解决识别错误、低效代码以及这些问题。智能合约具有不可篡改的特点&#xff0c;这使得审计成为任何区块链项目安全流程的关键部分。 代码审计对任何应用程序都很重要&#xff0c;…

【ES】Elasticsearch核心基础概念:文档与索引

es的核心概念主要是&#xff1a;index(索引)、Document(文档)、Clusters(集群)、Node(节点)与实例&#xff0c;下面我们先来了解一下Document与Index。 RESTful APIs 在讲解Document与Index概念之前&#xff0c;我们先来了解一下RESTful APIs&#xff0c;因为下面讲解Documen…