欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Android Okio的超時(shí)機(jī)制

 更新時(shí)間:2023年02月17日 11:08:10   作者:程序員小北  
Okio是一個(gè)IO庫,底層基于Java原生的輸入輸出流實(shí)現(xiàn)。但原生的輸入輸出流并沒有提供超時(shí)的檢測(cè)機(jī)制。而Okio實(shí)現(xiàn)了這個(gè)功能,本文就來為大家詳細(xì)講講

Okio是一個(gè)IO庫,底層基于Java原生的輸入輸出流實(shí)現(xiàn)。但原生的輸入輸出流并沒有提供超時(shí)的檢測(cè)機(jī)制。而Okio實(shí)現(xiàn)了這個(gè)功能。建議讀者先閱讀 Android | 徹底理解 Okio 之源碼篇 ,然后再閱讀本篇內(nèi)容會(huì)更好理解。

Timeout 類的設(shè)計(jì)

探討超時(shí)機(jī)制,首先要了解Timeout這個(gè)類。Timeout實(shí)現(xiàn)了Okio的同步超時(shí)檢測(cè),這里的同步指的是“任務(wù)執(zhí)行”和“超時(shí)檢測(cè)”是同步的,有順序的。同步超時(shí)不會(huì)直接中斷任務(wù)執(zhí)行,它首先會(huì)檢查是否發(fā)生超時(shí),然后決定是否中斷任務(wù)執(zhí)行。throwIfReached就是一個(gè)同步超時(shí)檢測(cè)的方法。

理解 timeout 與 deadline 的區(qū)別

timeout中文意為“超時(shí)”,deadline中文意為“最后期限”,它們是有明顯區(qū)別的。 Timeout類中有一系列的timeoutXxx方法,timeoutXxx是用來設(shè)置**一次操作完成的最大等待時(shí)間。若這個(gè)操作在等待時(shí)間內(nèi)沒有結(jié)束,則認(rèn)為超時(shí)。 deadlineXxx系列方法則是用來設(shè)置一項(xiàng)任務(wù)完成的最大等待時(shí)間。**意味著在未來多長(zhǎng)時(shí)間內(nèi),需要將這項(xiàng)任務(wù)完成,否則認(rèn)為超時(shí)。它可能包含一次或多次的操作。

讀取文件的例子

回顧下之前Okio讀取文件例子。

public void readFile() {
    try {
        FileInputStream fis = new FileInputStream("test.txt");
        okio.Source source = Okio.source(fis);
        BufferedSource bs = Okio.buffer(source);
        source.timeout().deadline(1, TimeUnit.MILLISECONDS);
        String res = bs.readUtf8();
        System.out.println(res);
    } catch (Exception e){
        e.printStackTrace();
    }
}

在這個(gè)例子中,我們使用deadline設(shè)置了超時(shí)時(shí)間為1ms,這意味著從現(xiàn)在開始,讀取文件的這項(xiàng)任務(wù),必須在未來的1ms內(nèi)完成,否則認(rèn)為超時(shí)。而讀取文件的這項(xiàng)任務(wù),就包含了多次的文件讀取操作。

搖骰子的例子

我們?cè)賮砜聪旅孢@個(gè)搖骰子的程序。Dice是一個(gè)骰子類,roll方法表示搖骰子,搖出來的點(diǎn)數(shù)latestTotal不會(huì)超過12。rollAtFixedRate會(huì)開啟一個(gè)線程,每隔一段時(shí)間調(diào)用roll方法搖一次骰子。awaitTotal方法會(huì)當(dāng)骰子的點(diǎn)數(shù)與我們傳遞進(jìn)去的total值一樣或者超時(shí)而結(jié)束。

private class Dice {
    Random random = new Random();
    int latestTotal;

    // 搖骰子
    public synchronized void roll() {
        latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
        System.out.println("Rolled " + latestTotal);
        notifyAll();
    }

    // 開啟一個(gè)線程,每隔一段時(shí)間執(zhí)行 roll 方法
    public void rollAtFixedRate(int period, TimeUnit timeUnit) {
      Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
        public void run() {
          roll();
         }
      }, 0, period, timeUnit);
    }

    // 超時(shí)檢測(cè)
    public synchronized void awaitTotal(Timeout timeout, int total) throws InterruptedIOException {
      while (latestTotal != total) {
        timeout.waitUntilNotified(this);
      }
   }
}

timeout()是一個(gè)測(cè)試骰子類的方法,在主線程中運(yùn)行。該程序設(shè)置每隔3s搖一次骰子,主線程設(shè)置超時(shí)時(shí)間為6s,期望搖到的點(diǎn)數(shù)是20。因?yàn)樵O(shè)置的超時(shí)是timeoutXxx系列的方法,所以這里超時(shí)的意思是“只要我搖一次骰子的時(shí)間不超過6s,那么我就不會(huì)超時(shí),可以一直搖骰子”。因?yàn)閾u出骰子的最大點(diǎn)數(shù)是12,而期望值是20,永遠(yuǎn)也搖不出來20這個(gè)點(diǎn)數(shù),且搖一次骰子的時(shí)間是3s多,也不滿足超時(shí)的時(shí)間。所以主線程就會(huì)一直處于等待狀態(tài)。

public void timeout(){
    try {
        Dice dice = new Dice();
        dice.rollAtFixedRate(3, TimeUnit.SECONDS);
        Timeout timeout = new Timeout();
        timeout.timeout(6, TimeUnit.SECONDS);
        dice.awaitTotal(timeout, 20);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

現(xiàn)在將timeout()方法修改一下,將timeout.timeout(6, TimeUnit.SECONDS)改為timeout.deadline(6, TimeUnit.SECONDS),之前我們說過deadlineXxx設(shè)置的超時(shí)**意味著在未來多長(zhǎng)時(shí)間內(nèi),需要將這項(xiàng)任務(wù)完成。**在搖骰子這里的意思就是“從現(xiàn)在開始,我只可以搖6s的骰子。超過這個(gè)時(shí)間你還在搖,則認(rèn)為超時(shí)”。它關(guān)注的是可以搖多久的骰子,而不是搖一次骰子不能超過多久的時(shí)間。

public void timeout(){
    try {
        Dice dice = new Dice();
        dice.rollAtFixedRate(3, TimeUnit.SECONDS);
        Timeout timeout = new Timeout();
        timeout.deadline(6, TimeUnit.SECONDS);
        dice.awaitTotal(timeout, 20);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

上述程序,主線程會(huì)在6s后因超時(shí)而停止等待,結(jié)束運(yùn)行。

等待直到喚醒

前面舉了兩個(gè)例子讓大家理解Okio中timeoutdeadline的區(qū)別。在搖骰子的例子中用到了waitUntilNotified這個(gè)方法來檢測(cè)超時(shí),中文意思為“等待直到喚醒”。也就是Java多線程中經(jīng)典的“等待-喚醒”機(jī)制,該機(jī)制常常用于多線程之間的通信。調(diào)用waitUntilNotified方法的線程會(huì)一直處于等待狀態(tài),除非被喚醒或者因超時(shí)而拋出異常。下面是該方法的源碼。

public final void waitUntilNotified(Object monitor) throws InterruptedIOException {
    try {
      boolean hasDeadline = hasDeadline();
      long timeoutNanos = timeoutNanos();

      // 若沒有設(shè)置 deadline && timeout,則一直等待直到喚醒
      if (!hasDeadline && timeoutNanos == 0L) {
        monitor.wait(); // There is no timeout: wait forever.
        return;
      }

      // Compute how long we'll wait.
      // 計(jì)算等待的時(shí)長(zhǎng),若同時(shí)設(shè)置了deadline 和 timeout,則 deadline 優(yōu)先
      long waitNanos;
      long start = System.nanoTime();
      if (hasDeadline && timeoutNanos != 0) {
        long deadlineNanos = deadlineNanoTime() - start;
        waitNanos = Math.min(timeoutNanos, deadlineNanos);
      } else if (hasDeadline) {
        waitNanos = deadlineNanoTime() - start;
      } else {
        waitNanos = timeoutNanos;
      }

      // Attempt to wait that long. This will break out early if the monitor is notified.
      long elapsedNanos = 0L;
      if (waitNanos > 0L) {
        long waitMillis = waitNanos / 1000000L;
        // 等待 waitNanos
        monitor.wait(waitMillis, (int) (waitNanos - waitMillis * 1000000L));
        // 計(jì)算從等待 waitNanos 到喚醒所用時(shí)間
        elapsedNanos = System.nanoTime() - start;
      }

      // Throw if the timeout elapsed before the monitor was notified.
      // 若等待了 waitNanos 還沒喚醒,認(rèn)為超時(shí)
      if (elapsedNanos >= waitNanos) {
        throw new InterruptedIOException("timeout");
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt(); // Retain interrupted status.
      throw new InterruptedIOException("interrupted");
    }
}

查看waitUntilNotified的源碼,我們發(fā)現(xiàn)該方法基于“等待-通知”機(jī)制,添加了多線程之間的超時(shí)檢測(cè)功能,一個(gè)線程用來執(zhí)行具體的任務(wù),一個(gè)線程調(diào)用該方法來檢測(cè)超時(shí)。在Okio中的管道就使用了waitUntilNotified這個(gè)方法。

AsyncTimeout 類的設(shè)計(jì)

AsyncTimeout內(nèi)部維護(hù)一個(gè)單鏈表,節(jié)點(diǎn)的類型是AsyncTimeout,以到超時(shí)之前的剩余時(shí)間升序排序,即超時(shí)的剩余時(shí)間越大,節(jié)點(diǎn)就在鏈表越后的位置。對(duì)鏈表的操作,使用了synchronized關(guān)鍵字加類鎖,保證在同一時(shí)間,只有一個(gè)線程可以對(duì)鏈表進(jìn)行修改訪問操作。

AsyncTimeout實(shí)現(xiàn)了Okio的異步超時(shí)檢測(cè)。這里的異步指的是“任務(wù)執(zhí)行”和“超時(shí)檢測(cè)”是異步的,在執(zhí)行任務(wù)的同時(shí),也在進(jìn)行任務(wù)的“超時(shí)檢測(cè)”。你會(huì)覺得這和上面搖骰子的例子很像,一個(gè)線程執(zhí)行任務(wù),一個(gè)線程檢測(cè)超時(shí)。事實(shí)上,AsyncTimeout也正是這樣實(shí)現(xiàn)的,它內(nèi)部的Watchdog線程就是用來檢測(cè)超時(shí)的。當(dāng)我們要對(duì)一次操作或一項(xiàng)任務(wù)設(shè)置超時(shí),使用成對(duì)的enter()exit(),模板代碼如下。

enter();
// do something
exit();

若上面do something的操作超時(shí),timedOut()方法將會(huì)在Watchdog線程被回調(diào)??梢钥匆姡@種包裹性的模板代碼,靈活性很大,我們幾乎可以在其中放置任何想要檢測(cè)超時(shí)的一個(gè)或多個(gè)操作。

AsyncTimeout 成員變量

下面是AsyncTimeout類主要的成員變量。

private static final long IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
static @Nullable AsyncTimeout head;
private boolean inQueue;
private @Nullable AsyncTimeout next;
private long timeoutAt;
  • IDLE_TIMEOUT_MILLIS,在單鏈表中沒有節(jié)點(diǎn)時(shí),Watchdog線程等待的時(shí)間
  • head,單鏈表的頭結(jié)點(diǎn),是一個(gè)虛假節(jié)點(diǎn)。當(dāng)鏈表中只存在該節(jié)點(diǎn),認(rèn)為該鏈表為空。
  • inQueue,當(dāng)前節(jié)點(diǎn)是否在鏈表中。
  • next,當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。
  • timeoutAt,以當(dāng)前時(shí)間為基準(zhǔn),當(dāng)前節(jié)點(diǎn)在將來何時(shí)超時(shí)。

AsyncTimeout 成員方法

scheduleTimeout 有序的將超時(shí)節(jié)點(diǎn)加入到鏈表中

scheduleTimeout方法可以將一個(gè)超時(shí)節(jié)點(diǎn)按照超時(shí)的剩余時(shí)間有序的插入到鏈表當(dāng)中。注意該方法使用synchronized修飾,是一個(gè)同步方法,可以保證對(duì)鏈表的操作是線程安全的。

private static synchronized void scheduleTimeout(AsyncTimeout node, long timeoutNanos, boolean hasDeadline) {
    // Start the watchdog thread and create the head node when the first timeout is scheduled.
    // 若 head 節(jié)點(diǎn)為 null, 初始化 head 并啟動(dòng) Watchdog 線程
    if (head == null) {
      head = new AsyncTimeout();
      new Watchdog().start();
    }

    // 計(jì)算 node 節(jié)點(diǎn)的 timeoutAt 值
    long now = System.nanoTime();
    if (timeoutNanos != 0 && hasDeadline) {
      // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around,
      // Math.min() is undefined for absolute values, but meaningful for relative ones.
      node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now);
    } else if (timeoutNanos != 0) {
      node.timeoutAt = now + timeoutNanos;
    } else if (hasDeadline) {
      node.timeoutAt = node.deadlineNanoTime();
    } else {
      throw new AssertionError();
    }

    // Insert the node in sorted order.
    // 返回 node 節(jié)點(diǎn)的超時(shí)剩余時(shí)間
    long remainingNanos = node.remainingNanos(now);
    // 從 head 節(jié)點(diǎn)開始遍歷鏈表, 將 node 節(jié)點(diǎn)插入到合適的位置
    for (AsyncTimeout prev = head; true; prev = prev.next) {
      // 若當(dāng)前遍歷的節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)為 null 或者 node 節(jié)點(diǎn)的超時(shí)剩余時(shí)間小于下一個(gè)節(jié)點(diǎn)
      if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) {
        // 將 node 節(jié)點(diǎn)插入到鏈表
        node.next = prev.next;
        prev.next = node;
        // 若當(dāng)前遍歷的節(jié)點(diǎn)是 head, 喚醒 watchdog 線程
        if (prev == head) {
          AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front.
        }
        break;
      }
    }
}

Watchdog 線程

scheduleTimeout方法中,若headnull,則會(huì)初始化head并啟動(dòng)Watchdog線程。Watchdog是一個(gè)守護(hù)線程,因此它會(huì)隨著JVM進(jìn)程的結(jié)束而結(jié)束。前面我們說過Watchdog線程是用來檢測(cè)超時(shí)的,它會(huì)逐個(gè)檢查鏈表中的超時(shí)節(jié)點(diǎn)是否超時(shí),直到鏈表中所有節(jié)點(diǎn)檢查完畢后結(jié)束運(yùn)行。

private static final class Watchdog extends Thread {
    Watchdog() {
      super("Okio Watchdog");
      setDaemon(true);
    }

    public void run() {
      while (true) {
        try {
          // 超時(shí)的節(jié)點(diǎn)
          AsyncTimeout timedOut;
          // 加鎖,同步代碼塊
          synchronized (AsyncTimeout.class) {
            // 等待節(jié)點(diǎn)超時(shí)
            timedOut = awaitTimeout();

            // Didn't find a node to interrupt. Try again.
            // 當(dāng)前該節(jié)點(diǎn)沒有超時(shí),繼續(xù)檢查
            if (timedOut == null) continue;

            // The queue is completely empty. Let this thread exit and let another watchdog thread
            // get created on the next call to scheduleTimeout().
            // 鏈表中已經(jīng)沒有超時(shí)節(jié)點(diǎn),結(jié)束運(yùn)行
            if (timedOut == head) {
              head = null;
              return;
            }
          }

          // Close the timed out node.
          // timedOut 節(jié)點(diǎn)超時(shí),回調(diào) timedOut() 方法
          timedOut.timedOut();
        } catch (InterruptedException ignored) {
        }
      }
    }
}

awaitTimeout 等待節(jié)點(diǎn)超時(shí)

Watchdog線程中會(huì)調(diào)用awaitTimeout方法來等待檢測(cè)的節(jié)點(diǎn)超時(shí),若檢測(cè)的節(jié)點(diǎn)沒有超時(shí),該方法返回null。否則返回超時(shí)的節(jié)點(diǎn)。

static @Nullable AsyncTimeout awaitTimeout() throws InterruptedException {
    // Get the next eligible node.
    // 檢測(cè)的節(jié)點(diǎn)
    AsyncTimeout node = head.next;

    // The queue is empty. Wait until either something is enqueued or the idle timeout elapses.
    // 若鏈表為空
    if (node == null) {
      long startNanos = System.nanoTime();
      // Watchdog 線程等待 60s,期間會(huì)釋放類鎖
      AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS);
      // 等待 60s 后若鏈表還為空則返回 head,否則返回 null
      return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS
              ? head  // The idle timeout elapsed.
              : null; // The situation has changed.
    }
    // node 節(jié)點(diǎn)超時(shí)剩余的時(shí)間
    long waitNanos = node.remainingNanos(System.nanoTime());

    // The head of the queue hasn't timed out yet. Await that.
    // node 節(jié)點(diǎn)超時(shí)剩余的時(shí)間 > 0,說明 node 還未超時(shí),繼續(xù)等待 waitNanos 后返回 null
    if (waitNanos > 0) {
      // Waiting is made complicated by the fact that we work in nanoseconds,
      // but the API wants (millis, nanos) in two arguments.
      long waitMillis = waitNanos / 1000000L;
      waitNanos -= (waitMillis * 1000000L);
      AsyncTimeout.class.wait(waitMillis, (int) waitNanos);
      return null;
    }

    // The head of the queue has timed out. Remove it.
    // node 節(jié)點(diǎn)超時(shí)了,將 node 從鏈表中移除并返回
    head.next = node.next;
    node.next = null;
    return node;
}

enter 進(jìn)入超時(shí)檢測(cè)

分析完上面三個(gè)方法后再來看enter就非常的簡(jiǎn)單了,enter內(nèi)部調(diào)用了scheduleTimeout方法來添加一個(gè)超時(shí)節(jié)點(diǎn)到鏈表當(dāng)中,而Watchdog線程隨即會(huì)開始檢測(cè)超時(shí)。

public final void enter() {
    if (inQueue) throw new IllegalStateException("Unbalanced enter/exit");
    long timeoutNanos = timeoutNanos();
    boolean hasDeadline = hasDeadline();
    if (timeoutNanos == 0 && !hasDeadline) {
      return; // No timeout and no deadline? Don't bother with the queue.
    }
    // 更新 inQueue 為 true
    inQueue = true;
    scheduleTimeout(this, timeoutNanos, hasDeadline);
}

exit 退出超時(shí)檢測(cè)

前面說過,enterexit在檢測(cè)超時(shí)是需要成對(duì)出現(xiàn)的。它們之間的代碼就是需要檢測(cè)超時(shí)的代碼。exit方法的返回值表示enterexit中間檢測(cè)的代碼是否超時(shí)。

public final boolean exit() {
    if (!inQueue) return false;
    // 更新 inQueue 為 false
    inQueue = false;
    return cancelScheduledTimeout(this);
}

cancelScheduledTimeout方法會(huì)將當(dāng)前的超時(shí)節(jié)點(diǎn)從鏈表中移除。為了保證對(duì)鏈表的操作是線程安全的,該方法也是一個(gè)同步方法。我們知道在awaitTimeout方法中,若某個(gè)節(jié)點(diǎn)超時(shí)了會(huì)將它從鏈表中移除。那么當(dāng)調(diào)用cancelScheduledTimeout發(fā)現(xiàn)node不在鏈表中,則一定表明node超時(shí)了。

private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) {
    // Remove the node from the linked list.
    // 若 node 在鏈表中,將其移除。
    for (AsyncTimeout prev = head; prev != null; prev = prev.next) {
      if (prev.next == node) {
        prev.next = node.next;
        node.next = null;
        return false;
      }
    }

    // The node wasn't found in the linked list: it must have timed out!
    // node 不在鏈表中,則 node 一定超時(shí)了,返回 true
    return true;
}

總結(jié)

本文詳細(xì)講解了Okio中超時(shí)機(jī)制的實(shí)現(xiàn)原理,主要是TimeoutAsyncTimeout類的源碼分析與解讀。相信大家已經(jīng)掌握了這部分知識(shí),現(xiàn)總結(jié)一下文中要點(diǎn)。

  • Okio 基于等待-喚醒機(jī)制,使用Watchdog線程來檢測(cè)超時(shí)。
  • 當(dāng)要對(duì)某項(xiàng)操作或任務(wù)進(jìn)行超時(shí)檢測(cè)時(shí),將它們放到enterexit的中間。
  • Okio 對(duì)鏈表的使用非常頻繁,在文件讀寫和超時(shí)檢測(cè)都使用到了鏈表這個(gè)結(jié)構(gòu)。

以上就是深入了解Android Okio的超時(shí)機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Android Okio超時(shí)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論