Android14 Handle机制

Handle是进程内部, 线程之间的通信机制.

handle主要接受子线程发送的数据, 并用此数据配合主线程更新UI

handle可以分发Message对象和Runnable对象到主线程中, 每个handle实例, 都会绑定到创建他的线程中, 它有两个作用,:

(1) 安排消息在某个主线程中某个地方执行

(2) 安排一个动作在不同的线程中执行

Handle, Lopper, MessegeQueue, Message的关系

handle机制中, 主要牵涉的类有如下四个, 它们分工明确, 但又互相作用

1. Message:消息

2. hanlder:消息的发起者

3. Lopper: 消息的遍历者

4.MessageQueue 消息队列

1.Loop类

  public final class Looper {@UnsupportedAppUsagestatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();@UnsupportedAppUsageprivate static Looper sMainLooper;  // guarded by Looper.classprivate static Observer sObserver;@UnsupportedAppUsagefinal MessageQueue mQueue;final Thread mThread;private boolean mInLoop;266      public static void loop() {
267          final Looper me = myLooper();
268          if (me == null) {
269              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
270          }
271          if (me.mInLoop) {
272              Slog.w(TAG, "Loop again would have the queued messages be executed"
273                      + " before this one completed.");
274          }
275  
276          me.mInLoop = true;
277  
278          // Make sure the identity of this thread is that of the local process,
279          // and keep track of what that identity token actually is.
280          Binder.clearCallingIdentity();
281          final long ident = Binder.clearCallingIdentity();
282  
283          // Allow overriding a threshold with a system prop. e.g.
284          // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
285          final int thresholdOverride =
286                  SystemProperties.getInt("log.looper."
287                          + Process.myUid() + "."
288                          + Thread.currentThread().getName()
289                          + ".slow", -1);
290  
291          me.mSlowDeliveryDetected = false;
292  
293          for (;;) {
294              if (!loopOnce(me, ident, thresholdOverride)) {
295                  return;
296              }
297          }
298      }330      private Looper(boolean quitAllowed) {
331          mQueue = new MessageQueue(quitAllowed);
332          mThread = Thread.currentThread();
333      }

 创建Looper对象的时候,同时创建了MessageQueue,并让Looper绑定当前线程。但我们从来不直接调用构造方法获取Looper对象,而是使用Looper的prepare()方法

108      public static void prepare() {
109          prepare(true);
110      }
111  
112      private static void prepare(boolean quitAllowed) {
113          if (sThreadLocal.get() != null) {
114              throw new RuntimeException("Only one Looper may be created per thread");
115          }
116          sThreadLocal.set(new Looper(quitAllowed));
117      }
118  126      @Deprecated
127      public static void prepareMainLooper() {
128          prepare(false);
129          synchronized (Looper.class) {
130              if (sMainLooper != null) {
131                  throw new IllegalStateException("The main Looper has already been prepared.");
132              }
133              sMainLooper = myLooper();
134          }
135      }

prepare()使用ThreadLocal 保存当前Looper对象,ThreadLocal 类可以对数据进行线程隔离,保证了在当前线程只能获取当前线程的Looper对象,同时prepare()保证当前线程有且只有一个Looper对象,间接保证了一个线程只有一个MessageQueue对象

prepareMainLooper统在启动App时,已经帮我们调用了

266      public static void loop() {
267          final Looper me = myLooper();
268          if (me == null) {
269              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
270          }
271          if (me.mInLoop) {
272              Slog.w(TAG, "Loop again would have the queued messages be executed"
273                      + " before this one completed.");
274          }
275  
276          me.mInLoop = true;
277  
278          // Make sure the identity of this thread is that of the local process,
279          // and keep track of what that identity token actually is.
280          Binder.clearCallingIdentity();
281          final long ident = Binder.clearCallingIdentity();
282  
283          // Allow overriding a threshold with a system prop. e.g.
284          // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
285          final int thresholdOverride =
286                  SystemProperties.getInt("log.looper."
287                          + Process.myUid() + "."
288                          + Thread.currentThread().getName()
289                          + ".slow", -1);
290  
291          me.mSlowDeliveryDetected = false;
292  
293          for (;;) {
294              if (!loopOnce(me, ident, thresholdOverride)) {
295                  return;
296              }
297          }
298      }

Lopper通过loop()开启无限循环,通过MessageQueue的next()获取message对象。一旦获取就调用msg.target.dispatchMEssage(msg)将msg交给handler对象处理(msg.target是handler对象)

所以Looper.loop的作用就是:从当前线程的MessageQueue从不断取出Message,并调用其相关的回调方法。

2. handler类

  1. 创建Handler对象

  2. 得到当前线程的Looper对象,并判断是否为空

  3. 让创建的Handler对象持有Looper、MessageQueu、Callback的引用

    214      public Handler(@Nullable Callback callback, boolean async) {
    215          if (FIND_POTENTIAL_LEAKS) {
    216              final Class<? extends Handler> klass = getClass();
    217              if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
    218                      (klass.getModifiers() & Modifier.STATIC) == 0) {
    219                  Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
    220                      klass.getCanonicalName());
    221              }
    222          }
    223  
    224          mLooper = Looper.myLooper();
    225          if (mLooper == null) {
    226              throw new RuntimeException(
    227                  "Can't create handler inside thread " + Thread.currentThread()
    228                          + " that has not called Looper.prepare()");
    229          }
    230          mQueue = mLooper.mQueue;
    231          mCallback = callback;
    232          mAsynchronous = async;
    233          mIsShared = false;
    234      }
    235  

    使用Handler发送消息主要有两种,一种是sendXXXMessage方式,还有一个postXXX方式,不过两种方式最后都会调用到sendMessageDelayed方法

    下面为sendMessage方式

    发送消息,将消息插入队列:sendMessage->sendMessageDelayed->sendMessageAtTime->enqueueMessage,这里会决定message是否为同步消息或者异步消息

  4. 727      public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    728          MessageQueue queue = mQueue;
    729          if (queue == null) {
    730              RuntimeException e = new RuntimeException(
    731                      this + " sendMessageAtTime() called with no mQueue");
    732              Log.w("Looper", e.getMessage(), e);
    733              return false;
    734          }
    735          return enqueueMessage(queue, msg, uptimeMillis);
    736      }
    778      private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
    779              long uptimeMillis) {
    780          msg.target = this;
    781          msg.workSourceUid = ThreadLocalWorkSource.getUid();
    782  
    783          if (mAsynchronous) {
    784              msg.setAsynchronous(true);
    785          }
    786          return queue.enqueueMessage(msg, uptimeMillis);
    787      }
    549      boolean enqueueMessage(Message msg, long when) {
    550          if (msg.target == null) {
    551              throw new IllegalArgumentException("Message must have a target.");
    552          }
    553  
    554          synchronized (this) {
    // 一个message只能呢个发送一次
    555              if (msg.isInUse()) {
    556                  throw new IllegalStateException(msg + " This message is already in use.");
    557              }
    558  
    559              if (mQuitting) {
    560                  IllegalStateException e = new IllegalStateException(
    561                          msg.target + " sending message to a Handler on a dead thread");
    562                  Log.w(TAG, e.getMessage(), e);
    563                  msg.recycle();
    564                  return false;
    565              }
    566  // 标记message已经使用
    567              msg.markInUse();
    568              msg.when = when;
    // 拿到头部
    569              Message p = mMessages;
    570              boolean needWake;
    571              if (p == null || when == 0 || when < p.when) {
    572                  // New head, wake up the event queue if blocked.
    // 将消息插入到头部, 指向刚才拿到的消息
    573                  msg.next = p;
    574                  mMessages = msg;
    575                  needWake = mBlocked;
    576              } else {
    // 根据需要把消息插入到消息队列的合适位置,通常是调用xxxDelay方法,延时发送消息
    577                  // Inserted within the middle of the queue.  Usually we don't have to wake
    578                  // up the event queue unless there is a barrier at the head of the queue
    579                  // and the message is the earliest asynchronous message in the queue.
    580                  needWake = mBlocked && p.target == null && msg.isAsynchronous();
    581                  Message prev;
    582                  for (;;) {
    583                      prev = p;
    584                      p = p.next;
    585                      if (p == null || when < p.when) {
    586                          break;
    587                      }
    588                      if (needWake && p.isAsynchronous()) {
    589                          needWake = false;
    590                      }
    591                  }
    592                  msg.next = p; // invariant: p == prev.next
    593                  prev.next = msg;
    594              }
    595  
    596              // We can assume mPtr != 0 because mQuitting is false.
    597              if (needWake) {
    598                  nativeWake(mPtr);
    599              }
    600          }
    601          return true;
    602      }

    //getPostMessage方法是两种发送消息的不同之处

    Post这个方法我们发现也是将我们传入的参数封装成了一个消息,只是这次是m.callback = r,刚才是msg.what=what,至于Message的这些属性就不看了

    434      public final boolean post(@NonNull Runnable r) {
    435         return  sendMessageDelayed(getPostMessage(r), 0);
    436      }943      private static Message getPostMessage(Runnable r) {
    944          Message m = Message.obtain();
    945          m.callback = r;
    946          return m;
    947      }

    post方法只是先调用了getPostMessage方法,用Runnable去封装一个Message,然后就调用了sendMessageDelayed,把封装的Message加入到MessageQueue中。

    所以使用handler发送消息的本质都是:把Message加入到Handler中的MessageQueue中去

    97      public void dispatchMessage(@NonNull Message msg) {
    98          if (msg.callback != null) {
    99              handleCallback(msg);
    100          } else {
    101              if (mCallback != null) {
    102                  if (mCallback.handleMessage(msg)) {
    103                      return;
    104                  }
    105              }
    106              handleMessage(msg);
    107          }
    108      }
    109  

    可以看出,这个方法就是从MessageQueue中取出Message以后,进行分发处理。

    首先,判断msg.callback是不是空,其实msg.callback是一个Runnable对象,是Handler.post方式传递进来的参数。而hanldeCallback就是调用的Runnable的run方法。

    然后,判断mCallback是否为空,这是一个Handler.Callback的接口类型,之前说了Handler有多个构造方法,可以提供设置Callback,如果这里不为空,则调用它的hanldeMessage方法,注意,这个方法有返回值,如果返回了true,表示已经处理 ,不再调用Handler的handleMessage方法;如果mCallback为空,或者不为空但是它的handleMessage返回了false,则会继续调用Handler的handleMessage方法,该方法就是我们经常重写的那个方法。

    3. MessageQueue

  5. 318      @UnsupportedAppUsage
    319      Message next() {
    320          // Return here if the message loop has already quit and been disposed.
    321          // This can happen if the application tries to restart a looper after quit
    322          // which is not supported.
    323          final long ptr = mPtr;
    324          if (ptr == 0) {
    325              return null;
    326          }
    327  
    328          int pendingIdleHandlerCount = -1; // -1 only during first iteration
    329          int nextPollTimeoutMillis = 0;
    330          for (;;) {
    331              if (nextPollTimeoutMillis != 0) {
    332                  Binder.flushPendingCommands();
    333              }
    334  
    335              nativePollOnce(ptr, nextPollTimeoutMillis);
    336  
    337              synchronized (this) {
    338                  // Try to retrieve the next message.  Return if found.
    339                  final long now = SystemClock.uptimeMillis();
    340                  Message prevMsg = null;
    341                  Message msg = mMessages;
    342                  if (msg != null && msg.target == null) {
    343                      // Stalled by a barrier.  Find the next asynchronous message in the queue.
    344                      do {
    345                          prevMsg = msg;
    346                          msg = msg.next;
    347                      } while (msg != null && !msg.isAsynchronous());
    348                  }
    349                  if (msg != null) {
    350                      if (now < msg.when) {
    351                          // Next message is not ready.  Set a timeout to wake up when it is ready.
    352                          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    353                      } else {
    354                          // Got a message.
    355                          mBlocked = false;
    356                          if (prevMsg != null) {
    357                              prevMsg.next = msg.next;
    358                          } else {
    359                              mMessages = msg.next;
    360                          }
    361                          msg.next = null;
    362                          if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    363                          msg.markInUse();
    364                          return msg;
    365                      }
    366                  } else {
    367                      // No more messages.
    368                      nextPollTimeoutMillis = -1;
    369                  }
    370  
    371                  // Process the quit message now that all pending messages have been handled.
    372                  if (mQuitting) {
    373                      dispose();
    374                      return null;
    375                  }
    376  
    377                  // If first time idle, then get the number of idlers to run.
    378                  // Idle handles only run if the queue is empty or if the first message
    379                  // in the queue (possibly a barrier) is due to be handled in the future.
    380                  if (pendingIdleHandlerCount < 0
    381                          && (mMessages == null || now < mMessages.when)) {
    382                      pendingIdleHandlerCount = mIdleHandlers.size();
    383                  }
    384                  if (pendingIdleHandlerCount <= 0) {
    385                      // No idle handlers to run.  Loop and wait some more.
    386                      mBlocked = true;
    387                      continue;
    388                  }
    389  
    390                  if (mPendingIdleHandlers == null) {
    391                      mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
    392                  }
    393                  mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    394              }
    395  
    396              // Run the idle handlers.
    397              // We only ever reach this code block during the first iteration.
    398              for (int i = 0; i < pendingIdleHandlerCount; i++) {
    399                  final IdleHandler idler = mPendingIdleHandlers[i];
    400                  mPendingIdleHandlers[i] = null; // release the reference to the handler
    401  
    402                  boolean keep = false;
    403                  try {
    404                      keep = idler.queueIdle();
    405                  } catch (Throwable t) {
    406                      Log.wtf(TAG, "IdleHandler threw exception", t);
    407                  }
    408  
    409                  if (!keep) {
    410                      synchronized (this) {
    411                          mIdleHandlers.remove(idler);
    412                      }
    413                  }
    414              }
    415  
    416              // Reset the idle handler count to 0 so we do not run them again.
    417              pendingIdleHandlerCount = 0;
    418  
    419              // While calling an idle handler, a new message could have been delivered
    420              // so go back and look again for a pending message without waiting.
    421              nextPollTimeoutMillis = 0;
    422          }
    423      }

    next方法中,首先设置一个死循环,然后调用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一个native方法,用于阻塞MessageQueue,主要是关注它的第二个参数nextPollTimeoutMillis,有如下三种可能:

如果nextPollTimeoutMillis=-1,一直阻塞不会超时。

如果nextPollTimeoutMillis=0,不会阻塞,立即返回。

如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果其间有程序唤醒会立即返回。

我们先继续往下看,开始nextPollTimeoutMillis为0,也就是不会阻塞,则继续往下,这时候有三种情况

1.延时消息,则直接算出目前需要延时的时间nextPollTimeoutMillis,注意,这时候并没有把消息返回,而是继续往下,设置mBlocked为true,表示消息队列已阻塞,并continue执行for循环体,再次执行nativePollOnce方法,这时候nextPollTimeoutMillis>0,则会导致MessageQueue休眠nextPollTimeoutMillis毫秒,接着应该会走到情况2.

2.不是延时消息,则设置mBlocked为false,表示消息队列没有阻塞,直接把消息返回,且把消息出队。

3. 如果消息为空,则调用位置nextPollTimeoutMillis为-1,继续往下,设置mBlocked为true,表示消息队列已阻塞,并continue继续for循环,这时候调用nativePollOnce会一直阻塞,且不会超时。

所以,当消息队列为空时,其实是调用本地方法nativePollOnce,且第二个参数为-1,它会导致当前线程阻塞,且不会超时

4. Message

Message分为3种:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,

因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。

屏障消息和普通消息的区别是屏障消息没有tartget。

1.屏障的插队方法

屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。

public int postSyncBarrier() {
473          return postSyncBarrier(SystemClock.uptimeMillis());
474      }
475  
476      private int postSyncBarrier(long when) {
477          // Enqueue a new sync barrier token.
478          // We don't need to wake the queue because the purpose of a barrier is to stall it.
479          synchronized (this) {
480              final int token = mNextBarrierToken++;
481              final Message msg = Message.obtain();
482              msg.markInUse();
483              msg.when = when;
484              msg.arg1 = token;
485  
486              Message prev = null;
487              Message p = mMessages;
488              if (when != 0) {
489                  while (p != null && p.when <= when) {
490                      prev = p;
491                      p = p.next;
492                  }
493              }
494              if (prev != null) { // invariant: p == prev.next
495                  msg.next = p;
496                  prev.next = msg;
497              } else {
498                  msg.next = p;
499                  mMessages = msg;
500              }
501              return token;
502          }
503      }
504  
  • 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
  • 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
  • postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  • postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
  • 插入普通消息会唤醒消息队列,但是插入屏障不会。

2.屏障的工作原理

通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。

通过messaQequeue中的next(),如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。可以看到通过这种方式就挡住了所有的普通消息

3,同步消息还是异步消息的决定时间

Handler有几个构造方法,可以传入async标志为true,这样构造的Handler发送的消息就是异步消息。不过可以看到,这些构造函数都是hide的,正常我们是不能调用的,不过利用反射机制可以使用@hide方法。

/*** @hide*/
public Handler(boolean async) {}/*** @hide*/
public Handler(Callback callback, boolean async) { }/*** @hide*/
public Handler(Looper looper, Callback callback, boolean async) {}

也可以直接调用message.setAsynchronous(true);来进行设置为异步消息

509      public void setAsynchronous(boolean async) {
510          if (async) {
511              flags |= FLAG_ASYNCHRONOUS;
512          } else {
513              flags &= ~FLAG_ASYNCHRONOUS;
514          }
515      }

3.屏障的移除

移除屏障可以通过MessageQueue的removeSyncBarrier方法:

517      public void removeSyncBarrier(int token) {
518          // Remove a sync barrier token from the queue.
519          // If the queue is no longer stalled by a barrier then wake it.
520          synchronized (this) {
521              Message prev = null;
522              Message p = mMessages;
523              while (p != null && (p.target != null || p.arg1 != token)) {
524                  prev = p;
525                  p = p.next;
526              }
527              if (p == null) {
528                  throw new IllegalStateException("The specified message queue synchronization "
529                          + " barrier token has not been posted or has already been removed.");
530              }
531              final boolean needWake;
532              if (prev != null) {
533                  prev.next = p.next;
534                  needWake = false;
535              } else {
536                  mMessages = p.next;
537                  needWake = mMessages == null || mMessages.target != null;
538              }
539              p.recycleUnchecked();
540  
541              // If the loop is quitting then it is already awake.
542              // We can assume mPtr != 0 when mQuitting is false.
543              if (needWake && !mQuitting) {
544                  nativeWake(mPtr);
545              }
546          }
547      }

总结下Handler消息机制主要的四个类的功能

  1. Message:信息的携带者,持有了Handler,存在MessageQueue中,一个线程可以有多个
  2. Hanlder:消息的发起者,发送Message以及消息处理的回调实现,一个线程可以有多个Handler对象
  3. Looper:消息的遍历者,从MessageQueue中循环取出Message进行处理,一个线程最多只有一个
  4. MessageQueue:消息队列,存放了Handler发送的消息,供Looper循环取消息,一个线程最多只有一个

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

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

相关文章

Python学习之基础语法

一、HelloWorld 二、Python基础语法 2.1 字面量 定义&#xff1a;在代码中&#xff0c;被写下来的固定的值&#xff0c;称之为字面量。 常用的6种值的类型 字符串 Python中&#xff0c;字符串需要用双引号包围&#xff1b; 被双引号包围的都是字符串 666 13.14 "黑马…

深度学习预备知识(线性代数)

介绍&#xff1a; 深度学习是一种机器学习的方法&#xff0c;涉及到大量的线性代数运算。线性代数是研究向量空间和线性映射的数学学科。在深度学习中&#xff0c;线性代数常用于表示和处理输入数据和模型参数。下面是一些深度学习中常见的线性代数概念和运算&#xff1a; 1. …

数据结构之单链表及其实现!

目录 ​编辑 1. 顺序表的问题及思考 2.链表的概念结构和分类 2.1 概念及结构 2.2 分类 3. 单链表的实现 3.1 新节点的创建 3.2 打印单链表 3.3 头插 3.4 头删 3.5 尾插 3.6 尾删 3.7 查找元素X 3.8 在pos位置修改 3.9 在任意位置之前插入 3.10 在任意位置删除…

【python量化】基于okex API开发的海龟策略

介绍 基于okex api开发的海龟策略&#xff0c;okex海龟策略python实现方式。该程序目前只支持单品种&#xff0c;比如设置ETH后&#xff0c;只对ETH进行做多做空。该程序运行需要两样东西&#xff1a;apikey 和 标的 运行该程序之前&#xff0c;用户需要到okex网站去申请apiK…

嘉绩咨询:八位一体产业创新,赋能品牌新零售

探索新零售领域不断创新高峰的嘉绩咨询在今天全面展现了其“八位一体”产业创新模式&#xff0c;该模式旨在为新零售品牌提供全方位的赋能服务。立足于广州的企业战略导航专家&#xff0c;吹响了帮助中国品牌实现全球化发展的号角。 嘉绩咨询的核心业务涵盖招商教育、招商落地、…

Java学习笔记之IDEA的安装与下载以及相关配置

1 IDEA概述 ​IDEA全称IntelliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具。 集成环境&#xff1a; ​把代码编写&#xff0c;编译&#xff0c;执行&#xff0c;调试等多种功能综合到一起的开发工具。 2 IDEA…

使用TTL直接对esp32-cam进行烧录

首先你要有一个usb转TTL下载器和一个esp32-cam 然后我们要将IO0与GND短接 UOR->TXD UOT->RXD 3V3->3V3 GND->GND

[蜥蜴书Chapter2] -- 创建测试集

目录 一、规则 二、方法 1、seed 2、identifier 1&#xff09;选取一个身份号 2&#xff09;选取一定比例的身份号作为测试集 3&#xff09;身份号的选取&#xff1a; 3、利用scikit-learn&#xff1a; 1) 随机生成&#xff1a; 2&#xff09;注&#xff1a;分类 3&a…

Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify

文章目录 1.共享带来的问题1.1 临界区 Critical Section1.2 竞态条件 Race Condition 2. synchronized语法及理解2.1 方法上的 synchronized 3.变量的线程安全分析3.1.成员变量和静态变量是否线程安全&#xff1f;3.2.局部变量是否线程安全&#xff1f;3.2.1 局部变量线程安全分…

Pycharm安装,环境初次配置与运行第一个简单程序

一、Pycharm安装 1.在PyCharm官网中&#xff0c;找到社区版下载链接&#xff0c;下载Pycharm社区版&#xff0c;社区版免费 2.下载成功后&#xff0c;双击下载好的安装包&#xff0c;点击下一步后&#xff0c;点击“浏览”更改安装路径到C盘以外其他硬盘&#xff0c;点击“下…

数字化审计智慧

简析内部审计数字化转型的方法和路径 内部审计是一种独立的、客观的确认和咨询活动&#xff0c;包括鉴证、识别和分析问题以及提供管理建议和解决方案。狭义的数字化转型是指将企业经营管理和业务操作的各种行为、状态和结果用数字的形式来记录和存储&#xff0c;据此再对数据进…

ChatGPT预训练的奥秘:大规模数据、Transformer架构与自回归学习【文末送书-31】

文章目录 ChatGPT原理与架构ChatGPT的预训练ChatGPT的迁移学习ChatGPT的中间件编程 ChatGPT原理与架构&#xff1a;大模型的预训练、迁移和中间件编程【文末送书-31】 ChatGPT原理与架构 近年来&#xff0c;人工智能领域取得了巨大的进展&#xff0c;其中自然语言处理&#xf…

一款开源、免费、跨平台的Redis可视化管理工具

前言 经常有小伙伴在技术群里问&#xff1a;有什么好用的Redis可视化管理工具推荐的吗&#xff1f;, 今天大姚给大家分享一款我一直在用的开源、免费&#xff08;MIT License&#xff09;、跨平台的Redis可视化管理工具&#xff1a;Another Redis Desktop Manager。 Redis介绍…

【亲测有效】解决三月八号ChatGPT 发消息无响应!

背景 今天忽然发现 ChatGPT 无法发送消息&#xff0c;能查看历史对话&#xff0c;但是无法发送消息。 可能的原因 出现这个问题的各位&#xff0c;应该都是点击登录后顶部弹窗邀请 [加入多语言 alapha 测试] 了&#xff0c;并且语言选择了中文&#xff0c;抓包看到 ab.chatg…

【Python】成功解决ModuleNotFoundError: No module named ‘matplotlib‘

【Python】成功解决ModuleNotFoundError: No module named ‘matplotlib’ &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448…

PyCharm如何添加python库

1.使用pip命令在国内源下载需要的库 下面使用清华源&#xff0c;在cmd中输入如下命令就可以了 pip install i https://pypi.tuna.tsinghua.edu.cn/simple 包名版本号2.如果出现报错信息&#xff0c;Cannot unpack file…这种情况&#xff0c;比如下面这种 ERROR: Cannot unpa…

数据结构奇妙旅程之二叉平衡树

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

图神经网络实战(4)——基于Node2Vec改进嵌入质量

图神经网络实战&#xff08;4&#xff09;——基于Node2Vec改进嵌入质量 0. 前言1. Node2Vec 架构1.2 定义邻居1.2 在随机游走中引入偏向性1.3 实现有偏随机游走 2. 实现 Node2Vec小结系列链接 0. 前言 Node2Vec 是一种基于 DeepWalk 的架构&#xff0c;DeepWalk 主要由随机游…

qt 格式化打印 日志 QMessagePattern 格式词法语法及设置

一、qt源码格式化日志 关键内部类 QMessagePattern qt为 格式化打印日志 提供了一个简易的 pattern(模式/格式) 词法解析的简易的内部类QMessagePattern,作用是获取和解析自定义的日志格式信息。 该类在qt的专门精心日志操作的源码文件Src\qtbase\src\corelib\global\qloggi…

[LeetCode][226]翻转二叉树

题目 226. 翻转二叉树 给你一棵二叉树的根节点 root&#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#x…