主頁 > 後端開發 > SynchronousQueue詳解

SynchronousQueue詳解

2022-10-13 07:52:52 後端開發

SynchronousQueue介紹

  【1】SynchronousQueue是一個沒有資料緩沖的BlockingQueue,生產者執行緒對其的插入操作put必須等待消費者的移除操作take,

             

  【2】如圖所示,SynchronousQueue 最大的不同之處在于,它的容量為 0,所以沒有一個地方來暫存元素,導致每次取資料都要先阻塞,直到有資料被放入;同理,每次放資料的時候也會阻塞,直到有消費者來取

  【3】需要注意的是,SynchronousQueue 的容量不是 1 而是 0,因為 SynchronousQueue 不需要去持有元素,它所做的就是直接傳遞(direct handoff),由于每當需要傳遞的時候,SynchronousQueue 會把元素直接從生產者傳給消費者,在此期間并不需要做存盤,所以如果運用得當,它的效率是很高的,

SynchronousQueue的原始碼分析

  【1】建構式

//默認采用非公平
public SynchronousQueue() {
    this(false);
}
//可以選擇模式
public SynchronousQueue(boolean fair) {
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

  【2】核心方法分析

//這些方法本質上都是呼叫屬性值transferer的transfer方法
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    if (transferer.transfer(e, false, 0) == null) {
        Thread.interrupted();
        throw new InterruptedException();
    }
}

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    return transferer.transfer(e, true, 0) != null;
}

public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    if (transferer.transfer(e, true, unit.toNanos(timeout)) != null)
        return true;
    if (!Thread.interrupted())
        return false;
    throw new InterruptedException();
}

public E take() throws InterruptedException {
    E e = transferer.transfer(null, false, 0);
    if (e != null)
        return e;
    Thread.interrupted();
    throw new InterruptedException();
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    E e = transferer.transfer(null, true, unit.toNanos(timeout));
    if (e != null || !Thread.interrupted())
        return e;
    throw new InterruptedException();
}

public E poll() {
    return transferer.transfer(null, true, 0);
}

s

Transferer分析

  【1】Transferer是SynchronousQueue的內部抽象類,雙堆疊和雙佇列演算法共享該類,他只有一個transfer方法,用于轉移元素,從生產者轉移到消費者;或者消費者呼叫該方法從生產者取資料,

  【2】Transferer有兩個實作類:TransferQueue和TransferStack,

  【3】這兩個類的區別就在于是否公平,TransferQueue是公平的,TransferStack非公平,

  【4】原始碼展示

// 堆疊和佇列共同的介面,負責執行 put or take
abstract static class Transferer<E> {
    // e 為空的,會直接回傳特殊值,不為慷訓傳遞給消費者
    // timed 為 true,說明會有超時時間
    abstract E transfer(E e, boolean timed, long nanos);
}

 

TransferQueue分析

  【1】節點元素

//佇列節點元素
static final class QNode {
    // 當前元素的下一個元素
    volatile QNode next;          
    // 當前元素的值,如果當前元素被阻塞住了,等其他執行緒來喚醒自己時,其他執行緒會把自己 set 到 item 里面
    volatile Object item;         
    // 可以阻塞住的當前執行緒
    volatile Thread waiter;       
    // 節點型別:true是 put,false是 take
    final boolean isData;         

   ....
}

  【2】構造方法

//佇列頭結點指標
transient volatile QNode head;
//佇列尾結點指標
transient volatile QNode tail;

TransferQueue() {
    QNode h = new QNode(null, false); // initialize to dummy node.
    head = h;
    tail = h;
}

  【3】核心方法

@SuppressWarnings("unchecked")
E transfer(E e, boolean timed, long nanos) {

    QNode s = null; 
    //根據是否傳入資料 判斷是獲取還是存放 
    boolean isData = https://www.cnblogs.com/chafry/p/(e != null);

    for (;;) {
        // 佇列頭和尾的臨時變數,佇列是空的時候,t=h
        QNode t = tail;
        QNode h = head;
        // tail 和 head 沒有初始化時,無限回圈,雖然這種 continue 非常耗cpu,但感覺不會碰到這種情況
        // 因為 tail 和 head 在 TransferQueue 初始化的時候,就已經被賦值空節點了
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin
        // 首尾節點相同,說明是空佇列
        // 或者尾節點的操作和當前節點操作一致
        if (h == t || t.isData =https://www.cnblogs.com/chafry/p/= isData) { // empty or same-mode
            QNode tn = t.next;
            if (t != tail)                  //直至拿到尾節點
                continue;
            if (tn != null) {               // lagging tail
                advanceTail(t, tn);
                continue;
            }
            //超時直接回傳 null
            if (timed && nanos <= 0)        // can't wait
                return null;
            //構建新節點
            if (s == null)
                s = new QNode(e, isData);
            //將新建節點塞入佇列
            if (!t.casNext(null, s))        // failed to link in
                continue;

            advanceTail(t, s);             
            // 阻塞住自己
            Object x = awaitFulfill(s, e, timed, nanos);
            if (x == s) {                   // wait was cancelled
                clean(t, s);
                return null;
            }

            if (!s.isOffList()) {           // not already unlinked
                advanceHead(t, s);          // unlink if head
                if (x != null)              // and forget fields
                    s.item = s;
                s.waiter = null;
            }
            return (x != null) ? (E)x : e;

        } 
        // 佇列不為空,并且當前操作和隊尾不一致,也就是說當前操作是隊尾是對應的操作
        // 比如說隊尾是因為 take 被阻塞的,那么當前操作必然是 put
        else {
            // 也就是這行代碼體現出佇列的公平,每次操作時,從頭開始按照順序進行操作
            QNode m = h.next;              
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read

            Object x = m.item;
            if (isData =https://www.cnblogs.com/chafry/p/= (x != null) ||    // m already fulfilled
                x == m ||                   // m cancelled
                !m.casItem(x, e)) {         // lost CAS
                advanceHead(h, m);          // dequeue and retry
                continue;
            }
            // 當前操作放到隊頭
            advanceHead(h, m);           
            // 釋放隊頭阻塞節點
            LockSupport.unpark(m.waiter);
            return (x != null) ? (E)x : e;
        }
    }
}

 

TransferStack分析

  【1】節點元素

// 堆疊中節點的幾種型別:
// 1. 消費者(請求資料的)
static final int REQUEST    = 0;
// 2. 生產者(提供資料的)
static final int DATA       = https://www.cnblogs.com/chafry/p/1;
// 3. 二者正在匹配中
static final int FULFILLING = 2;

// 堆疊中的節點
static final class SNode {
    // 下一個節點
    volatile SNode next;        
    volatile SNode match;       // the node matched to this
    // 等待著的執行緒
    volatile Thread waiter;    
    Object item;                
    // 模式,也就是節點的型別,是消費者,是生產者,還是正在匹配中
    int mode;
...
}

 

  【2】核心方法

// TransferStack.transfer()方法
E transfer(E e, boolean timed, long nanos) {
    SNode s = null; // constructed/reused as needed
    // 根據e是否為null決定是生產者還是消費者
    int mode = (e == null) ? REQUEST : DATA;
    // 自旋+CAS
    for (;;) {
        // 堆疊頂元素
        SNode h = head;
        // 堆疊頂沒有元素,或者堆疊頂元素跟當前元素是一個模式的
        // 也就是都是生產者節點或者都是消費者節點
        if (h == null || h.mode == mode) {  // empty or same-mode
            // 如果有超時而且已到期
            if (timed && nanos <= 0) {      // can't wait
                // 如果頭節點不為空且是取消狀態
                if (h != null && h.isCancelled())
                    // 就把頭節點彈出,并進入下一次回圈
                    casHead(h, h.next);     // pop cancelled node
                else
                    // 否則,直接回傳null(超時回傳null)
                    return null;
            } else if (casHead(h, s = snode(s, e, h, mode))) {
                // 入堆疊成功(因為是模式相同的,所以只能入堆疊)
                // 呼叫awaitFulfill()方法自旋+阻塞當前入堆疊的執行緒并等待被匹配到
                SNode m = awaitFulfill(s, timed, nanos);
                // 如果m等于s,說明取消了,那么就把它清除掉,并回傳null
                if (m == s) {               // wait was cancelled
                    clean(s);
                    // 被取消了回傳null
                    return null;
                }
                
                // 到這里說明匹配到元素了
                // 因為從awaitFulfill()里面出來要不被取消了要不就匹配到了
                // 如果頭節點不為空,并且頭節點的下一個節點是s
                // 就把頭節點換成s的下一個節點
                // 也就是把h和s都彈出了
                // 也就是把堆疊頂兩個元素都彈出了
                if ((h = head) != null && h.next == s)
                    casHead(h, s.next);     // help s's fulfiller
                // 根據當前節點的模式判斷回傳m還是s中的值
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
        } else if (!isFulfilling(h.mode)) { // try to fulfill
            // 到這里說明頭節點和當前節點模式不一樣
            // 如果頭節點不是正在匹配中
            
            // 如果頭節點已經取消了,就把它彈出堆疊
            if (h.isCancelled())            // already cancelled
                casHead(h, h.next);         // pop and retry
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                // 頭節點沒有在匹配中,就讓當前節點先入隊,再讓他們嘗試匹配
                // 且s成為了新的頭節點,它的狀態是正在匹配中
                for (;;) { // loop until matched or waiters disappear
                    SNode m = s.next;       // m is s's match
                    // 如果m為null,說明除了s節點外的節點都被其它執行緒先一步匹配掉了
                    // 就清空堆疊并跳出內部回圈,到外部回圈再重新入堆疊判斷
                    if (m == null) {        // all waiters are gone
                        casHead(s, null);   // pop fulfill node
                        s = null;           // use new node next time
                        break;              // restart main loop
                    }
                    SNode mn = m.next;
                    // 如果m和s嘗試匹配成功,就彈出堆疊頂的兩個元素m和s
                    if (m.tryMatch(s)) {
                        casHead(s, mn);     // pop both s and m
                        // 回傳匹配結果
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                  // lost match
                        // 嘗試匹配失敗,說明m已經先一步被其它執行緒匹配了
                        // 就協助清除它
                        s.casNext(m, mn);   // help unlink
                }
            }
        } else {                            // help a fulfiller
            // 到這里說明當前節點和頭節點模式不一樣
            // 且頭節點是正在匹配中
            
            SNode m = h.next;               // m is h's match
            if (m == null)                  // waiter is gone
                // 如果m為null,說明m已經被其它執行緒先一步匹配了
                casHead(h, null);           // pop fulfilling node
            else {
                SNode mn = m.next;
                // 協助匹配,如果m和s嘗試匹配成功,就彈出堆疊頂的兩個元素m和s
                if (m.tryMatch(h))          // help match
                    // 將堆疊頂的兩個元素彈出后,再讓s重新入堆疊
                    casHead(h, mn);         // pop both h and m
                else                        // lost match
                    // 嘗試匹配失敗,說明m已經先一步被其它執行緒匹配了
                    // 就協助清除它
                    h.casNext(m, mn);       // help unlink
            }
        }
    }
}

// 三個引數:需要等待的節點,是否需要超時,超時時間
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
    // 到期時間
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 當前執行緒
    Thread w = Thread.currentThread();
    // 自旋次數
    int spins = (shouldSpin(s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
        // 當前執行緒中斷了,嘗試清除s
        if (w.isInterrupted())
            s.tryCancel();
        
        // 檢查s是否匹配到了元素m(有可能是其它執行緒的m匹配到當前執行緒的s)
        SNode m = s.match;
        // 如果匹配到了,直接回傳m
        if (m != null)
            return m;
        
        // 如果需要超時
        if (timed) {
            // 檢查超時時間如果小于0了,嘗試清除s
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel();
                continue;
            }
        }
        if (spins > 0)
            // 如果還有自旋次數,自旋次數減一,并進入下一次自旋
            spins = shouldSpin(s) ? (spins-1) : 0;
        
        // 后面的elseif都是自旋次數沒有了
        else if (s.waiter == null)
            // 如果s的waiter為null,把當前執行緒注入進去,并進入下一次自旋
            s.waiter = w; // establish waiter so can park next iter
        else if (!timed)
            // 如果不允許超時,直接阻塞,并等待被其它執行緒喚醒,喚醒后繼續自旋并查看是否匹配到了元素
            LockSupport.park(this);
        else if (nanos > spinForTimeoutThreshold)
            // 如果允許超時且還有剩余時間,就阻塞相應時間
            LockSupport.parkNanos(this, nanos);
    }
}

// SNode里面的方向,呼叫者m是s的下一個節點
// 這時候m節點的執行緒應該是阻塞狀態的
boolean tryMatch(SNode s) {
    // 如果m還沒有匹配者,就把s作為它的匹配者
    if (match == null &&
        UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
        Thread w = waiter;
        if (w != null) {    // waiters need at most one unpark
            waiter = null;
            // 喚醒m中的執行緒,兩者匹配完畢
            LockSupport.unpark(w);
        }
        // 匹配到了回傳true
        return true;
    }
    // 可能其它執行緒先一步匹配了m,回傳其是否是s
    return match == s;
}

 

SynchronousQueue總結

  【1】是一個沒有資料緩沖的BlockingQueue,容量為0,它不會為佇列中元素維護存盤空間,它只是多個執行緒之間資料交換的媒介,

  【2】資料結構:鏈表,在其內部類中維護了資料

      先消費(take),后生產(put);

        第一個執行緒Thread0是消費者訪問,此時佇列為空,則入隊(創建Node結點并賦值)

        第二個執行緒Thread1也是消費者訪問,與隊尾模式相同,繼續入隊

        第三個執行緒Thread2是生產者,攜帶了資料e,與隊尾模式不同,不進行入隊操作,直接將該執行緒攜帶的資料e回傳給隊首的消費者,并喚醒隊首執行緒Thread1(默認非公平策略是堆疊結構),出隊,

      反之,先生產(put)后消費(take),原理一樣

  【3】鎖:CAS+自旋(無鎖)【阻塞:自旋了一定次數后呼叫 LockSupport.park()

  【4】存取呼叫同一個方法:transfer()

      put、offer 為生產者,攜帶了資料 e,為 Data 模式,設定到 SNode或QNode 屬性中,

      take、poll 為消費者,不攜帯資料,為 Request 模式,設定到 SNode或QNode屬性中,

  【5】程序

      執行緒訪問阻塞佇列,先判斷隊尾節點或者堆疊頂節點的 Node 與當前入隊模式是否相同

      相同則構造節點 Node 入隊,并阻塞當前執行緒,元素 e 和執行緒賦值給 Node 屬性

      不同則將元素 e(不為 null) 回傳給取資料執行緒,隊首或堆疊頂執行緒被喚醒,出隊

  【6】公平模式:TransferQueue,隊尾匹配(判斷模式),隊頭出隊,先進先出

  【7】非公平模式(默認策略):TransferStack,堆疊頂匹配,堆疊頂出堆疊,后進先出

  【8】應用場景

      SynchronousQueue非常適合傳遞性場景做交換作業,生產者的執行緒和消費者的執行緒同步傳遞某些資訊、事件或者任務,

      SynchronousQueue的一個使用場景是在執行緒池里,如果我們不確定來自生產者請求數量,但是這些請求需要很快的處理掉,那么配合SynchronousQueue為每個生產者請求分配一個消費執行緒是處理效率最高的辦法,Executors.newCachedThreadPool()就使用了SynchronousQueue,這個執行緒池根據需要(新任務到來時)創建新的執行緒,如果有空閑執行緒則會重復使用,執行緒空閑了60秒后會被回收,

Transferer

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/514061.html

標籤:Java

上一篇:PriorityBlockingQueue詳解

下一篇:django 報錯 'set' object is not reversible 解決

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more