Handler 原理

news/2024/4/29 12:09:06/文章来源:https://blog.csdn.net/m0_49508485/article/details/127998204

线程的应用场景

Android是单线程模型,Activity、Service、Broadcast等组件的创建,都是在主线程完成的,即UI线程。但如果需要执行一些耗时的操作时,比如:I/O的读写、大文件的读写、数据库操作以及网络上传和下载等操作都需要很长的时间,如果主线程中有较多的耗时任务,就会降低界面的响应速度,甚至失去响应,如果失去响应超过 5秒,系统就会提示强行关闭程序。解决办法就是,使用子线程Thread。

线程与进程的区别

线程是进程内的一个执行单元,也是进程内的可调度实体。
两者的区别:
1.地址空间:每个进程都至少存在一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。
2.线程是CPU处理器调度的基本单位,但进程不是。
3.进程是资源分配,并且拥有单位的,同一个进程内的线程可以共享进程里的资源。

线程间通信

Handler机制,是Runnable 和 Activity交互的桥梁。在run中发送Message,在Handler里通过不同的Message,执行不同的任务。 

涉及的类:

Handler:负责消息的发送和处理,通过它可以实现子线程 和 主线程之间的通信。
Looper:  负责管理已接收的线程消息和消息队列。
Message: 消息载体。
MessageQueue:消息队列,遵循先进先出的原则,它用来保存待处理的线程消息。

线程安全问题

当使用多个子线程来同时访问一个数据时,会出现线程安全的问题,比如:多个线程同时操作同一个数据,会导致数据不一致的问题。所以我们一般通过同步机制来解决这样的问题:synchronized(){ }同步代码块 和synchronized 同步方法。

子线程中更新UI的几种方式

handle.handleMessage(Message msg);
handle.post(Runnable r);
runOnUiThread(Runnable r);
View.post(Runnable r);
子线程更新UI的详细解释看这里:面试官:子线程 真的不能更新UI ?

Handler机制

1. Handler定义

Handler是一套Android消息传递机制。

2. Handler的作用

在多线程应用场景中,将子线程中需要更新UI的操作消息,传递到UI主线程,从而实现子线程通知UI更新最终实现异步消息处理。
    

3 .相关概念

Message: 消息载体,里面存储这线程消息。
Handler. : 负责消息的发送和处理,子线程中使用sendMessage() 发送消息;在handleMessage()中处理。
MessageQueue:消息队列,遵循先进先出的原则,存储着sendMessage()发送来的子线程消息。
Looper: 消息循环器,负责从MessageQueue中循环取消息,再将取出的消息分发给 handleMessage(),来处理消息。每个线程只能有一个Looper (它是通过ThreadLocal实现的唯一性),即多个子线程可以借助同一个Handler进行通信。

4. Handler工作机制

1. Handler机制的工作流程:
  • Handler初始化: 在主线程中创建Looper、MessageQueue、Handler,创建完成后,Looper则自动进入消息循环状态 。同时,Handler自动绑定Looper和MessageQueue。如果消息队列为空,则线程阻塞。主线程中由于在ActivityThread的main()方法中已经初始化过了,所以就不需要在初始化Looper和MessageQueue了。
  • 子线程发送消息子线程中通过Handler向消息队列MessageQueue中发送消息。
  • 消息循环:           Looper 循环取出MessageQueue中的Message消息。Looper 将循环取出的消息分发给Handler中的handleMessage()方法。
  • 接收处理消息:    在handleMessage(Message msg)方法中处理消息。
2. Looper的作用
  • 创建Looper对象
  • 创建MessageQueue对象
  • 让Looper对象持有当前线程
    Looper相关方法:
  • Looper.prepare()———为当前线程创建一个Looper;
  • Looper.loop() ——— 开启消息循环;
  • Looper.prepareMainLooper() ——— 为主线程创建Looper时使用,在ActivityThread有用到。
  • Looper.getMainLooper() ——— 通过该方法可以获取主线程的Looper。
  • Looper.quit() ——— 退出Looper循环。
  • Looper.quitSafely() ——— 自己创建的Looper,在不使用的时候,需要退出。
3. Thread、Handler、Looper的对应关系
  • 一个Thread线程只能绑定到一个Looper循环器上,但可以有多个Handler实例处理者。
  • 一个Looper循环器可以绑定多个Handler实例。比如主线程的main()方法中创建了Looper和Handler对象,但是我们开发过程中仍然可以创建其他Handler对象
  • 一个Handler对象处理者,只能绑定到一个Looper循环器中。

5. Handler的使用

Handler的使用有两种方式:handler.sendMessage (Message msg)、handler.post (Runnable r)。
5.1 子线程向主线程发送消息
public class MainActivity extends Activity {private static final String TAG = "MainActivity";private Handler mHandler;private Button btnSendeToMainThread;private static final int MSG_SUB_TO_MAIN= 100;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnSendeToMainThread = (Button) findViewById(R.id.btn_sendto_mainthread);// 1.创建Handler,并重写handleMessage方法mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);// 处理消息switch (msg.what) {case MSG_SUB_TO_MAIN:// 打印出处理消息的线程名和Message.objLog.e(TAG, "接收到消息: " + Thread.currentThread().getName() + ","+ msg.obj);break;default:break;}}};btnSendeToMainThread.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 创建一个子线程,在子线程中发送消息new Thread(new Runnable() {@Overridepublic void run() {Message msg = Message.obtain();msg.what = MSG_SUB_TO_MAIN;msg.obj = "这是一个来自子线程的消息";// 2.发送消息mHandler.sendMessage(msg);}}).start();}});}
}
5.2 主线程向子线程发送消息
// 创建一个子线程,并在子线程中创建一个Handler,且重写handleMessage
new Thread(new Runnable() {@Overridepublic void run() {//子线程中创建Handler接收器,就必须创建Looper。Looper.prepare();subHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);// 处理消息switch (msg.what) {case MSG_MAIN_TO_SUB:Log.e(TAG, "接收到消息:" + Thread.currentThread().getName() + ","+ msg.obj);break;default:break;}}};Looper.loop();}
}).start();btnSendToSubThread = (Button) findViewById(R.id.btn_sendto_subthread);
btnSendToSubThread.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Message msg = Message.obtain();msg.what = MSG_MAIN_TO_SUB;msg.obj = "这是一个来自主线程的消息";// 主线程中发送消息subHandler.sendMessage(msg);}
});

6. Handler导致内存泄漏分析

下面的两种写法会导致内存泄漏。
/*** 方式1:新建Handler子类(普通内部类)*/  
public class MainActivity extends AppCompatActivity {public static final String TAG = "carson:";private Handler showhandler;// 主线程创建时便自动创建Looper & 对应的MessageQueue// 之后执行Loop()进入消息循环@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1. 实例化自定义的Handler类对象->>分析1//注:此处并无指定Looper,故自动绑定当前线程(主线程)的 Looper、MessageQueueshowhandler = new FHandler();// 2. 启动子线程1new Thread() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg = Message.obtain();msg.what = 1;// 消息标识msg.obj = "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3. 启动子线程2new Thread() {@Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg = Message.obtain();msg.what = 2;// 消息标识msg.obj = "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}// 分析1:自定义Handler子类class FHandler extends Handler {// 通过复写handlerMessage() 从而确定更新UI的操作@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}}
}
/*** 方式2:匿名Handler内部类*/
public class MainActivity extends AppCompatActivity {public static final String TAG = "carson:";private Handler showhandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//1. 通过匿名内部类实例化的Handler类对象showhandler = new  Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:Log.d(TAG, "收到线程1的消息");break;case 2:Log.d(TAG, " 收到线程2的消息");break;}}};// 2. 启动子线程1new Thread() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg = Message.obtain();msg.what = 1;// 消息标识msg.obj = "AA";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();// 3. 启动子线程2new Thread() {@Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// a. 定义要发送的消息Message msg = Message.obtain();msg.what = 2;// 消息标识msg.obj = "BB";// 消息存放// b. 传入主线程的Handler & 向其MessageQueue发送消息showhandler.sendMessage(msg);}}.start();}
}
      
1. 储备知识
在Java中,非静态内部类匿名内部类 都是会默认持有外部类的引用的。
主线程中的Looper对象的生命周期 = 该应用程序的生命周期
2. 内存泄漏原因描述
当Handler消息队列MessageQueue中,还有未处理的消息或者正在处理的消息时,消息队列中的Message会持有Handler实例的引用,而Handler实例又持有着外部类MainActivity的引用。此时如果外部类MainActivity被销毁,但由于上述的引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。
最终原因,是由于Looper的生命周期 > MainActivity的生命周期。
3. 内存泄漏的解决方案
     解决方案一:
     静态内部类 + 弱引用
     原理:静态内部类,默认不会持有外部类的引用。从而使得逐层的引用关系不复存在;
     弱引用对象拥有的生命周期很短暂。垃圾回收器执行扫描时,一旦发现了具有弱引用的对象,
     不管内存空间是否充足,都会回收这个弱引用对象的内存。
     解决方案二:
     外部类被销毁时,清空MessageQueue消息队列。
     这样不仅使得逐层的引用关系不复存在,同时使得Looper的生命周期与外部类实现了同步。
     清除消息队列的方法是:removeCallbackAndMessages(null);

7. Handler中的Looper如何停止

可以通过调用 handler.getLooper().quit(),执行完退出之后,Looper.loop() 后面的代码才会执行。否则后面的代码是永远都不会执行的。
public class LooperActivity extends AppCompatActivity {private static final String TAG = "LooperActivity";private Button btn;private Handler mHandler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_looper);btn = (Button) findViewById(R.id.btn);// 开启一个子线程,去执行异步任务new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}};Log.e(TAG, "Looper.loop之前" );// Looper.loop方法是一个死循环Looper.loop();// 得不到执行Log.e(TAG, "Looper.loop之后" );}}).start();btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 调用Looper的quit方法,停止LoopermHandler.getLooper().quit();}});}
}

8. Looper 死循环为什么不会导致应用卡死?

Launch桌面的图标第一次启动 Activity 时,会最终走到 ActivityThread 的 main 方法,在 main 方法里面创建了 Looper 和 MessageQueue 来处理主线程的消息,然后 Looper.loop 方法就进入死循环。我们的 Activity 的生命周期都是通过 Handler 机制来处理的。在看看看 loop 方法循环:

主线程主要依靠的就是消息循环,一旦退出消息循环,那么应用也就退出了。 Looper.loop() 方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理消息事件,就不会产生 ANR 异常。

造成 ANR 异常的不是主线程阻塞,而是主线程中 Looper 的消息处理过程发生了任务阻塞从而无法响应手势操作,不能及时刷新UI。

阻塞与ANR无必然关系,虽然主线程在没有消息可处理的时候是阻塞状态的,但是只要保证有消息的时候能够得到立即处理,程序就不会发生 ANR。

总结:应用卡死跟这个Looper 没有关系,应用在没有消息需要处理的时候,Looper 它是在睡眠,释放线程;卡死是 ANR,而 Looper 是睡眠。

9. MessageQueue源码详解

1. 源码-----插入消息
boolean enqueueMessage(Message msg, long when) {//即不允许添加同步消息屏障,实现异步消息了。if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {//. . . . . .msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;//当队列为空,第一次发来的 message,或者新发来的 message 的时间更小的情况,就走if块,添加到链表头部。if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;//如果此时Looper处于阻塞状态,则唤醒。并循环执行 message 的读取。needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.// 将发来的 message 插入到 queue 队列的中间或者尾部。// 这种情况下,我们不需要唤醒事件队列。除非有一个消息屏障在队列的头部,并且它是队列中最早的异步消息。// 判断当前发送来的 message 是否是一个异步消息,如果是异步消息,并且队列 queue头部含有 消息屏障// p.target = null, 同时当前 queue是处于阻塞状态。则设为 “需要唤醒队列”。 //这里的 p 消息,也有可能是 postAsyncBarrier()中传到 消息队列里的,因此此处需要处理异步消息的情况needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;// 执行一个 for循环,并通过时间的对比,将新发来的消息,插入到队列中适当的位置。for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}//如果队列头部有了 target=null 的一个消息屏障,并且当前发送过来的 message 是一个异步消息。// 如果添加 message 之前,队列中还有其他异步消息等待处理,则就不唤醒 queue。否则就唤醒。if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p;prev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;
}

2. 源码——Looper

public static void loop() {boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}try {// 将事件交由 Handler处理msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}// 回收msg,以便 Message.obtain() 实现复用。msg.recycleUnchecked();}
}

3. 源码——MessageQueue

Message next() {// mPtr,Native层的 MessageQueue。final long ptr = mPtr;if (ptr == 0) {return null;}int nextPollTimeoutMillis = 0;for (;;) {// Native层设置阻塞时间—延迟消息的时间nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages; // 获取链表头部信息if (msg != null && msg.target == null) {// Stalled by a barrier.  Find the next asynchronous message in the queue.// 循环遍历,查找第一个 异步消息。会直接忽略 queue 中的其他消息。do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.// 根据延迟消息,设置阻塞时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 返回一个即时消息,或者返回一个已到时间的 延迟消息。mBlocked = false; // 设置为 queue未阻塞状态if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse();return msg;}} else {// queue 队列中没有消息时,会返回 -1,表示 nativePollOnce() 会一直阻塞 nextPollTimeoutMillis = -1;}}}
}

4. 延迟消息Handler.postDelay(new Runnable(), 2000)

//使用方式
Handler handler = new Handler();
handler.postDelayed(new Runnable() {@Overridepublic void run() {//延迟一秒后,执行这里面的内容}
},1000);
handler.removeCallback();

传入的Runnable,最终存储在了mesage.callback 中。也是一种延迟消息,当到时间时,looper循环取出后,会执行Handler中的dispatchMessage()方法。判断如果 callback 不为空,就执行 callback.run()。callback就是一个Rnnable对象。

Hanlder同步消息屏障

原理:Android 源码分析 - Handler的同步屏障机制 - 简书
实例:Android中异步消息和同步屏障_xingzhong128的博客-CSDN博客
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);System.out.println("打印: " + msg.what);switch (msg.what){case 999://第四步 异步消息执行完之后必需,移除消息屏障,否则会一直阻塞 queue。removeSyncBarrier();break;}}};new Thread(new Runnable() {int max = 0;@Overridepublic void run() {while (max != 10) {handler.sendEmptyMessageDelayed(max, 5000 + 500 * max);max++;}}}).start();new Thread(new Runnable() {@Overridepublic void run() {Message message = Message.obtain();message.what = 999;// 第一步 设置 message为 异步消息message.setAsynchronous(true);// 第二步 设置消息屏障postSyncBarrier();// 第三步 发送消息handler.sendMessageDelayed(message, 0);}}).start();
}private int token = 0;//设置消息屏障时返回的token,用于删除消息屏障时使用。// 反射执行投递同步屏障
public void postSyncBarrier() {Method method = null;try {method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");token = (int) method.invoke(Looper.getMainLooper().getQueue());} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
}// 反射执行移除同步屏障
public void removeSyncBarrier() {Method method = null;try {method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);method.invoke(Looper.getMainLooper().getQueue(), token);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}
}

源码分析 --- 消息屏障

步骤一、Message设置为异步消息
步骤二 、在 queue中设置消息屏障
// 8.0之后该方法改为 @hide隐藏了,因此需要通过反射获取。
public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());
}
//注意⚠️ 这里添加message的时候,target没有设置,默认为null — — 即消息屏障
private int postSyncBarrier(long when) {// Enqueue a new sync barrier token.// We don't need to wake the queue because the purpose of a barrier is to stall it.synchronized (this) {final int token = mNextBarrierToken++;final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;// 从链表的头部开始,找出队列中最后一个message,或者找出queue 中间的延迟时间晚于当前message延迟时间的if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) { // 如果queue队列前面有其他message,则将当前 message插入到这两个 message中间 msg.next = p;prev.next = msg;} else { // 如果前面没有 message,则将当前的 message设置为最头部的 message。msg.next = p;mMessages = msg;}// 返回一个 token,用于接下来的 移除工作。return token;}
}

步骤三、发送消息

步骤四、移除消息屏障
public void removeSyncBarrier(int token) {// Remove a sync barrier token from the queue.// If the queue is no longer stalled by a barrier then wake it.synchronized (this) {Message prev = null;Message p = mMessages;while (p != null && (p.target != null || p.arg1 != token)) {prev = p;p = p.next;}if (p == null) {throw new IllegalStateException("The specified message queue synchronization "+ " barrier token has not been posted or has already been removed.");}final boolean needWake;if (prev != null) {prev.next = p.next;needWake = false;} else {mMessages = p.next;needWake = mMessages == null || mMessages.target != null;}p.recycleUnchecked();// If the loop is quitting then it is already awake.// We can assume mPtr != 0 when mQuitting is false.if (needWake && !mQuitting) {nativeWake(mPtr);}}
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于数据治理工具的选型,你了解多少?

数据治理的本质是盘点数据资产、治理数据质量&#xff0c;实施数据全生命周期的管理&#xff0c;这里面包括了建组织、立制度或者使用一款数据治理的软件帮助企业开展数据治理的相关工作等等。根据不同的数据治理项目特点&#xff0c;会用到不同的技术或工具。拥有一套趁手好用…

pdf生成:puppeteer

一、Puppeteer Puppeteer是Google Chrome团队出品的一款无界面Chrome工具&#xff0c;它提供了丰富的API&#xff0c;让开发者像鼠标一样控制浏览器的各种行为。Puppeteer是一个Node库&#xff0c;提供发了一个高级API来通过DevTools协议控制Chromium或Chrome。Puppeteer默认以…

LVGL学习笔记

芯片启动到LVGL初始化完成大体流程如下: 界面增加打印后代码如下: static void drag_event_handler(lv_event_t * e) {lv_obj_t * obj lv_event_get_target(e);lv_indev_t * indev lv_indev_get_act();if(indev NULL) return;lv_point_t vect;lv_indev_get_vect(indev, …

scala语法(一)(有java基础速学)

在拥有java基础上学习scala&#xff0c;注意以下几点 1. 变量声明 var | val 变量名 [: 变量类型] 变量值 val name: String "nico" 声明变量时&#xff0c;类型可以省略&#xff08;就是叫 类型推断&#xff09; val name "nico"类型确定后&#xff…

【面试题】JS基础-异步

1. 异步 1.1 为什么要异步&#xff1f; JS是单线程语言&#xff0c;只能同时做一件事。JS和DOM渲染共用同一个线程&#xff0c;因为JS可修改DOM结构。当遇到等待的情况时&#xff0c;例如网络请求、定时任务&#xff0c;程序不能卡住。所以需要异步来解决JS单线程等待的问题&…

Git -- submoudule子模块使用

文章目录子模块的作用添加子模块拉取带子模块的项目修改子模块代码子模块的作用 通常情况下&#xff0c;我们做项目时会有几个业务功能区分比较明确的模块&#xff0c;比如简单来说&#xff0c;一个项目我们可以分为认证授权模块、工具类模块、常规业务模块。 而像认证…

【Redis技术探索】「高可用架构模式」哨兵(sentinel)模式实现主从故障互切换模式详解

哨兵&#xff08;sentinel&#xff09;模式实现主从故障互切换模式详解Redis的多种模式Redis单机模式Redis单机模式的优点Redis单机模式的缺点Redis主从复制旧版本配置新版本配置查看主节点信息主从模式的优点主从复制的弊端Redis哨兵模式分析哨兵结构组成哨兵模式的主从切换Re…

重点,一文掌握ReentrantLock加解锁原理!|原创

本文详细讲解了 ReentrantLock 加锁和释放锁的原理&#xff0c;以及和 Synchronized 的对比。本文较长&#xff0c;建议收藏&#xff01;点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达简要总结 ReentrantLock实现原理&#xff1a;volati…

Android入门第33天-Android里的弹出式对话框

简介 Android Studio里在4.0前有一种ProgressDialog&#xff0c;这个已经淘汰了。我们完全可以使用ProgressBar来取代。但是还有一种Dialog叫PopWindow&#xff0c;它是一种“可阻塞式Dialog”。即弹出后除非你给它一个“动作”否则就一直显示在那。 今天我们就来看看这种Dia…

【Linux】基础IO —— 动静态库的制作与使用

&#x1f308;欢迎来到Linux专栏~~动静态库的制作与使用 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自…

Spring Boot 检索定时任务

概述 应用经常需要添加检索功能&#xff0c;开源的 ElasticSearch 是目前全文搜索引擎的首选。他可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持。 Elasticsearch是一个分布式搜索服务&#xff0c;提…

Unity3D占用内存太大怎么解决呢? -下

什么时候才是UnusedAssets?看一个例子&#xff1a; Object obj Resources.Load("MyPrefab"); GameObject instance Instantiate(obj) as GameObject; ......... Destroy(instance); 创建随后销毁了一个Prefab实例&#xff0c;这时候 MyPrefab已经没有被实际的物体…

5.XMLHttpRequest对象

XMLHttpRequest简称xhr&#xff0c;是浏览器提供的Javascript对象。之前我们使用的都是jQuery中的Ajax&#xff0c;现在我们使用原生JS的Ajax 目录 1 GET请求 1.1 不带参数请求 1.2 带参数请求 2 URL的编码与解码 2.1 编码 encodeURI() 2.2 解码 decodeURI() 3 …

【通用设计方法】之接收异常保护

目录 前言 一、接收异常保护 二、超短包、背靠背的支持 后记 前言 为了系统的鲁棒性&#xff0c;我们常常会做一系列的异常保护功能&#xff0c;避免系统挂死。 这里仅仅介绍接收保护的某些设计思路&#xff0c;抛砖引玉。 一、接收异常保护 设计思路&#xff1a;通过可配…

数据可视化大屏设计

在数据业务展示场景中&#xff0c;数据可视化大屏已经变得十分常见。那么在设计思路上&#xff0c;数据可视化大屏应当遵循什么样的设计逻辑&#xff1f;本篇文章里做了介绍&#xff0c;一起来看一下。 一、数据大屏的应用场景 1、大型会议 2、业务展示 二、数据大屏分类 1、展…

C语言源代码系列-管理系统之会员计费系统

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过…

【Python百日进阶-WEB开发-冲進Flask】Day183 - Flask数据库ORM基础、增加

文章目录一、day03项目环境和结构搭建1.1 flask-script1.1.1 flask-script是干什么的&#xff1f;1.1.2 flask-script安装1.1.3 flask-script的使用1.1.3.1 创建Manager实例1.1.3.2 初始化实例出错与解决1.1.4 终端启动1.1.4.1 查看runserver参数1.1.5 自定义添加manager命令1.…

linux篇【11】:linux下的线程<前序>

目录 一.linux下的线程 1.linux下的线程概念 &#xff08;1&#xff09;教材上粗略的 线程 定义 &#xff08;2&#xff09;线程的引入 &#xff08;3&#xff09;线程真正定义 以及 示意图 &#xff08;4&#xff09;linux 和 windows等其他操作系统的线程对比 &#xf…

新的趋势:From Big to Small and Wide data

新的趋势&#xff1a;From Big to Small and Wide data 所以&#xff0c;在这个时候&#xff0c;作为率先提出要做 MySQL 开源 HTAP 数据库的 StoneDB&#xff0c;想要稍微冷静一下。 不是说我们不做 HTAP 了&#xff0c;而是有了一个新的思路。这个思路&#xff0c;也同样来…

【亲测】网址引导页管理系统

介绍&#xff1a; 易航网址引导系统-网址引导页管理系统去授权版一款极其优雅的易航网址引导页管理系统&#xff0c; 如果有问题可以跟我反馈&#xff0c;共同进步。祝各位道友一路飞升&#xff0c;顶峰相见&#xff01;内置12套模板和防墙插件。 项目亮点&#xff1a; 1、…