主頁 >  其他 > 【JUC原始碼】SynchronousQueue吐血萬字原始碼深析(超詳細注釋)

【JUC原始碼】SynchronousQueue吐血萬字原始碼深析(超詳細注釋)

2020-10-01 14:00:23 其他

從類注釋可以得到關于SynchronousQueue的資訊:

  1. 佇列不存盤資料,所以沒有大小,也無法迭代,沒有大小如何理解呢?即每次進行put值進去時, 必須等待相應的 consumer 拿走資料后才可以再次 put 資料,
  2. queue 對應 peek, contains, clear, isEmpty … 等方法其實是無效的,
  3. 佇列由兩種資料結構組成,分別是后入先出的堆疊和先入先出的佇列,堆疊是非公平的,佇列是公平的,

1.結構

SynchronousQueue 繼承關系,核心成員變數及主要建構式:

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
	
	// Transferer 定義了transfer方法,put,take都是用的同一個transfer方法
	abstract static class Transferer<E>{
	    // e為空的,會直接回傳特殊值,不為慷訓傳遞給消費者
        abstract E transfer(E e, boolean timed, long nanos);
	}
	
	// 堆疊實作,后入先出(非公平)
	static final class TransferStack<E> extends Transferer<E>{...}
	
	// 佇列實作,先入先出(公平)
	static final class TransferQueue<E> extends Transferer<E>{...}
	
	// tranfer變數
	private transient volatile Transferer<E> transferer;
	
	//----------------------------------建構式---------------------------------
	// 默認非公平
	public SynchronousQueue() {
        this(false);
    }
    
    // 公平用TransferQueue,非公平就用TransferStack
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }
    
}

這里需要著重強調的是,SynchronousQueue 沒有使用鎖(synchronized 與 reentrantlock) ,因為鎖底層的佇列無法實作匹配,所以 SynchronousQueue 必須自己保證執行緒安全,并實作執行緒的調度:

  • 通過CAS與自旋實作執行緒安全
  • 直接對執行緒進行阻塞(park)與喚醒,其中有兩種策略,堆疊實作的非公平配對與佇列的公平配對

1.1 TransferStack(非公平=>FILO)

static final class TransferStack<E> extends Transferer<E>{
   
        // 堆疊中元素,這是一個鏈式堆疊
        static final class SNode{...}
        // 堆疊頭指標
        volatile SNode head;
		
		// SNode的三種狀態:
    	// 1.REQUEST:執行的是take方法,相當于消費者
        static final int REQUEST    = 0;
        // 2.DATA:執行的是put方法,相當于生產者
        static final int DATA       = 1;
        // 3.FULFILLING:堆疊頭正在阻塞等待其他執行緒進行 put 或 take
        static final int FULFILLING = 2;
    
    	//...
}

SNode

雖然 SynchronousQueue 的特性說的是里面是沒有元素,但這句話實際的意義是 SynchronousQueue 是一個不能 peek,contains 等操作的節點,但是里面是有一條鏈表來保存競爭的執行緒和資料

也就是說,整個SynchronousQueue的運行機制也還是通過維護一個鏈表來實作的,當有并發時,通過判斷鏈表插入一端節點的型別(mode),從而確定是否進行交換,

static final class SNode {
            
            // 當前執行緒
            // 注:不是在創建SNode時設定,而是在awaitFulfill方法中沒匹配到需要休眠時才會設定
            volatile Thread waiter;
            // 當前執行緒資料
            // 注:只有put的執行緒item才會有值,take的執行緒item=null,若take的執行緒要取出資料只能通過match指標為中介
            Object item;
    	    // 節點型別:REQUEST(0)-消費者(take) ,DATA(1)-生產者(put), FULFILLING(2)-交換中
            int mode;
			
			volatile SNode next;
            // 很重要的節點,表示和本節點配對(match)的節點,有兩個作用:
            // 1.判斷阻塞堆疊元素能被喚醒的時機
            //  比如執行緒A在take時由于佇列為空被阻塞了,然后執行緒B進行了put操作,那么就會將A的match設定為B,表示可以將A喚醒了
            // 2.作為take執行緒獲取資料交換的中介
            //  比如當執行緒A喚醒后要回傳資料,那么就可以通過match找到B,從而拿到put的資料,這個邏輯可以在下面的transfer方法可以看到
            volatile SNode match;
------------------------------------------------------------------------------------------------------------------			
			// 建構式,傳入item
			// 注:一般不直接構造,而是呼叫封裝好的snode方法:SNode snode(SNode s, Object e, SNode next, int mode) 
            SNode(Object item) {
                this.item = item;
            }
------------------------------------------------------------------------------------------------------------------			
    	    // 通過CAS把val節點連接到cmp后面
            boolean casNext(SNode cmp, SNode val) {
                return cmp == next &&
                       UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
            }
			
    		// tryMatch 非常重要的方法,兩個作用:
            // 1 嘗試將引數節點s,賦給當前執行緒的配對節點match
            // 2.喚醒被阻塞的堆疊頭的執行緒,醒后就能從 match 中得到本次操作 s
            // 其中 s.item 記錄著本次的操作節點,也就是記錄本次操作的資料
            boolean tryMatch(SNode s) {
                if (match == null &&
                    // CAS改變match來進行匹配,成功的條件是當前節點的match屬性為null
                    UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                    Thread w = waiter;
                    if (w != null) {    
                        // 將waiter置為null
                        waiter = null;
                        // 喚醒當前node的執行緒
                        LockSupport.unpark(w);
                    }
                    return true;
                }
                // 回傳是否配對成功
                return match == s; 
            }

           // 嘗試取消,就是把match換為自己,
		   // 自己匹配自己的前提是match為null,也就是說,如果已經匹配了,那么這個方法不能取消
    	   // 一般在設定超時且過期后,會將其設為cancel
            void tryCancel() {
                UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
            }
    
		   // 判斷超時失效
    	   // 和上一個方法對比,則可以知道,判斷match是不是自己
            boolean isCancelled() {
                return match == this;
            }

           // unsafe相關代碼...
}           

transfer():進堆疊&出堆疊

transfer 將 take 和 put 兩個方法都揉在了一起,所以第一個問題是如何區分put 和 take?是通過引數 e 是否為null來區分,e不為 null 是 put,為null是 take,第二個問題是如何進行執行緒管理的,或者說如何使 put 和 take 的執行緒配對的?具體分為以下三種情況:

  • 情況 1:佇列中還沒有資料 或 當前節點與堆疊頂節點同型別(同put或同take)
    • 情況 1.1:要加入的e設定了超時時間,并且 e 進堆疊或者出堆疊要超時了
      • 情況 1.1.1:堆疊頭不為null 且 堆疊頭已經超時失效,將堆疊頂置為第二個節點
      • 情況 1.1.2:堆疊頭是空的,回傳null
    • 情況 1.2:沒有設定新元素e的超時時間,或者設定了但未超時,
      1. 用e構造新節點s,使s.next=head,然后將s設為新的堆疊頭
      2. 阻塞等待與s匹配的節點m
      3. 若沒等到(s過期了),就呼叫clean洗掉s
      4. 若等到了,就讓s與m出堆疊,設定新的頭結點,并回傳
  • 情況 2: 當前堆疊包含于給定節點模式互補的節點(比如堆疊頂是put時阻塞,而當前節點是take操作)
    • 情況 2.1:堆疊頭已經被取消,將下一個節點置為堆疊頭
    • 情況 2.2:可以將當前節點s打上"正在匹配"的標記,并設定為head
      1. 取s的下一個節點m,s與m不斷tryMatch
      2. 匹配成功,洗掉s與m,回傳item
      3. 匹配失敗,交換m與m.next
      4. 若堆疊完了都沒匹配到則退出自旋,進入3
  • 情況 3:當動作2匹配失敗(可能同時執行緒3搶先完成了配對),幫助這個節點完成匹配和移除(出堆疊)的操作,然后繼續執行(主回圈),這部分代碼基本和動作2的代碼一樣,只是不會回傳節點的資料,
@SuppressWarnings("unchecked")
E transfer(E e, boolean timed, long nanos) {
    SNode s = null; 
    // 判斷是put還是take:e 為空是take方法(REQUEST),不為空是put方法(DATA)
    int mode = (e == null) ? REQUEST : DATA;
    // 自旋,保證一定能成功
    for (;;) {
        // 拿出頭節點,有幾種情況
        SNode h = head;
-----------------------------------------------------------------------------------------------------------------        
        // 情況 1:佇列中還沒有資料 || 當前節點與堆疊頂節點同型別(同put或同take)
        if (h == null || h.mode == mode) {
            // 情況 1.1:要加入的e設定了超時時間,并且 e 進堆疊或者出堆疊要超時了
            if (timed && nanos <= 0) {     
                // 情況 1.1.1:堆疊頭不為null && 堆疊頭已經超時失效
                if (h != null && h.isCancelled())
                    casHead(h, h.next); // 丟棄堆疊頭,把堆疊頭后一個元素作為堆疊頭
                // 情況 1.1.2:堆疊頭是空的
                else
                    return null; // 直接回傳 null
            // 情況 1.2:沒有設定新元素e的超時時間,或者設定了但未超時
            } else if (casHead(h, s = snode(s, e, h, mode))) { // 用e構造新節點s,使s.next=head,然后將s設為新的堆疊頭
                // 阻塞等待,目的是等到與s匹配的SNode
                SNode m = awaitFulfill(s, timed, nanos);
                // 回傳m==s代表當前節s點已經超時了
                if (m == s) {   
                    clean(s); // 堆疊中無法直接洗掉s,所以呼叫clean
                    return null;
                }
                // 只有真正匹配到值才能走到這一步
                // 堆疊不為空 && 堆疊二是s(因為等到的m此時是新堆疊頂)
                if ((h = head) != null && h.next == s)
                    // 將s.next設定為head,表示將s和他的配對m出堆疊
                    casHead(h, s.next);  
                // 回傳item
                // 注:這里回傳的是put的節點的資料,若是take節點那么需要通過中介m來獲取到資料
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
-----------------------------------------------------------------------------------------------------------------                    
        // 情況 2: 當前堆疊包含于給定節點模式互補的節點(比如堆疊頂是put時阻塞,而當前節點是take操作)
        } else if (!isFulfilling(h.mode)) { 
            // 情況 2.1:堆疊頭已經被取消
            if (h.isCancelled())            
                casHead(h, h.next); // 把下一個元素作為堆疊頭
            // 情況 2.2:可以將當前節點s打上"正在匹配"的標記,并設定為head
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                // 自旋,直到配對
                for (;;) { 
                    // m和s是正在匹配兩個節點
                    // 注:此時m不一定是之前的堆疊頂,因為這段時間可能又有節點進入或者之前的堆疊頂已被先一步匹配走了
                    SNode m = s.next;       // m is s's match
                    // 堆疊遍歷完了,都沒有找到
                    if (m == null) {        // all waiters are gone
                        casHead(s, null);   // pop fulfill node
                        s = null;           // use new node next time
                        break;              // restart main loop
                    }
                    // 獲取m的next節點,因為如果s和m匹配成功,mn就得補上head的位置了
                    // 注:雖然后面m可能會與mn交換,但在每輪回圈中mn一定是堆疊中第三個節點
                    SNode mn = m.next;
                    // 呼叫 tryMatch 讓 m和s 配對
                    // 注:這里呼叫tryMatch給s配對時,會喚醒阻塞在awaitFulfill方法的執行緒m,若配對失敗m還會回到阻塞狀態
                    if (m.tryMatch(s)) {
                        // 配對成功,彈出s與m,將head置為mn
                        casHead(s, mn);     
                        // 回傳put的資料,若是take節點那么需要通過中介m來獲取到資料
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else  
                        // 若配對失敗,將m與m.next交換,開始下一輪匹配
                        s.casNext(m, mn);   
                }
            }
-----------------------------------------------------------------------------------------------------------------                    
       	// 情況3:上面匹配失敗,可能是同時又執行緒3提前完成了配對  
        } else {                             
            SNode m = h.next;  
            // 堆疊里面沒有任何等待者了,其他節點把m匹配走了
            if (m == null)                 
                casHead(h, null);           // pop fulfilling node
            else {
                // 如果m和h匹配成功,則mn就成為新head了,
                SNode mn = m.next;
                if (m.tryMatch(h))          // help match
                    casHead(h, mn);         // pop both h and m
                else                        // lost match
                    h.casNext(m, mn);       // help unlink
            }
        }
    }
}

awaitFulfill():等待匹配節點

awaitFulfill 作用是阻塞等待匹配的節點,但不是一上來就阻塞住,而是在自旋一定次數后,仍然沒有其它執行緒來滿足自己的要求時,才會真正的阻塞住,等待其他執行緒transfer后tryMatch

  1. 計算死亡時間 deadline(時間戳)與自旋次數 spains
  2. 自旋,每次回圈都判斷s是否獲得到match
  3. 達到自旋次數,park當前執行緒(定時)
  4. 執行緒被喚醒時機:
    • tryMatch喚醒:其余執行緒在transfer中遍歷堆疊時呼叫 tryMatch喚醒,若匹配成功 return 相應 m,否則重新回到阻塞
    • 超時喚醒:return s(自己)
Node awaitFulfill(SNode s, boolean timed, long nanos) {
    // deadline 死亡時間,如果設定了超時時間的話,死亡時間等于當前時間 + 超時時間,否則就是 0
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 當前執行緒
    Thread w = Thread.currentThread();
    // 自旋的次數,如果設定了超時時間,會自旋 32 次,否則自旋 512 次
    int spins = (shouldSpin(s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0);
                 
    for (;;) {
        // 當前執行緒有無被打斷,如果過了超時時間,當前執行緒就會被打斷
        if (w.isInterrupted())
            s.tryCancel();
		
        // 嘗試獲取當前節點的match
        SNode m = s.match;
        // 該函式的唯一出口,一定要匹配到值
        // 回傳的 m==s 表示超時取消,回傳的 m!=s 表確實匹配到了
        if (m != null)
            return m;
        if (timed) {
            nanos = deadline - System.nanoTime();
            // 超時了,取消當前執行緒的等待操作
            if (nanos <= 0L) {
                // 呼叫cancel,使m=s
                s.tryCancel();
                continue;
            }
        }
        
        // 如果沒到自旋次數,那么自旋次數減少 1
        if (spins > 0)
            spins = shouldSpin(s) ? (spins-1) : 0; // 
        // 如果s沒設定waiter,那么把當前執行緒設定成 waiter
        else if (s.waiter == null)
            s.waiter = w; 
        // 如果沒有設定超時,那么直接park當前執行緒  
        else if (!timed)
            LockSupport.park(this); // 當被unpark喚醒時也是在此處,繼續回圈
        // 如果設定了超時,呼叫帶有nanos超時時間的park
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}

clean():清除過期節點

清除處于堆疊中的過期節點s,大致程序如下:

  1. 找到s的下一個沒有cancel的節點past
  2. 判斷head是否cancel
  3. 從head遍歷連接past
void clean(SNode s) {
            s.item = null;   // forget item 把item和waiter都置空
            s.waiter = null; // forget thread
			
            // 獲得下一個SNode
            SNode past = s.next;
    	   // 如果past被cancell了,那么就再past一個
            if (past != null && past.isCancelled())
                past = past.next;

            // 從頭節點開始清除
            SNode p;
    	    // 把頭節點鏈接到下一個節點,節點不能為cancelled
            while ((p = head) != null && p != past && p.isCancelled())
                casHead(p, p.next);

            // Unsplice embedded nodes
            while (p != null && p != past) {
                // 在去除鏈接頭節點以后的節點,同樣也不能為null,
                SNode n = p.next;
                if (n != null && n.isCancelled())
                    p.casNext(n, n.next);
                else
                    p = n;
            }
}

1.2 TransferQueue(公平=>FIFO)

static final class TransferQueue<E> extends Transferer<E>{
    // 佇列頭 
    transient volatile QNode head;
    // 佇列尾 
    transient volatile QNode tail;

    // 佇列的元素
    static final class QNode {...}
    
    //...
}

QNode

static final class QNode {

	// 當前元素的值,如果當前元素被阻塞住了,等其他執行緒來喚醒自己時,其他執行緒會把自己 set 到 item 里面
    volatile Object item;         // CAS'ed to or from null
    // 可以阻塞住的當前執行緒
    volatile Thread waiter;       // to control park/unpark
     // true 是 put,false 是 take
    final boolean isData;
    
    // 當前元素的下一個元素
    volatile QNode next;         
	
	// 構造時傳入資料和節點型別
	QNode(Object item, boolean isData) {
	    this.item = item;
	    this.isData = isData;
	}
	
	// 將節點val通過cas連接在cmp后面
	boolean casNext(QNode cmp, QNode val) {
	    return next == cmp &&
	        UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
	}
	
	// 將item的值通過cas變為val
	boolean casItem(Object cmp, Object val) {
	    return item == cmp &&
	        UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
	}
	
	/**
	 * Tries to cancel by CAS'ing ref to this as item.
	 */
	void tryCancel(Object cmp) {
	    UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
	}
	
	// 被取消的節點就是item=this
	boolean isCancelled() {
	    return item == this;
	}
	
	// unsafe相關代碼...
} 

transfer():入隊&出隊

當前執行緒是如何把自己的資料傳給阻塞執行緒的?為了方便說明,我們假設執行緒 1 往佇列中 take 資料 ,被阻塞住了,變成阻塞執行緒 A ,然后執行緒 2 開始往佇列中 put 資料 B,大致的流程是這樣的:

  1. 執行緒 1 從佇列中拿資料,發現佇列中沒有資料,于是被阻塞,成為 A ;
  2. 執行緒 2 往隊尾 put 資料,會從隊尾往前找到第一個被阻塞的節點,假設此時能找到的就是節點 A,然后執行緒 B 把將 put 的資料放到節點 A 的 item 屬性里面,并喚醒執行緒 1;
  3. 執行緒 1 被喚醒后,就能從 A.item 里面拿到執行緒 2 put 的資料了,執行緒 1 成功回傳,

從這個程序中,我們能看出公平主要體現在,每次 put 資料的時候,都 put 到隊尾上,而每次拿資料時,并不是直接從隊頭拿資料,而是從隊尾往前尋找第一個被阻塞的執行緒,這樣就會按照順序釋放被阻塞的執行緒

E transfer(E e, boolean timed, long nanos) {

    QNode s = null; // constructed/reused as needed
    // true 是 put,false 是 get
    boolean isData = (e != null);

    for (;;) {
        // 佇列頭和尾的臨時變數,佇列是空的時候,t=h
        QNode t = tail;
        QNode h = head;
        // tail 和 head 沒有初始化時,無限回圈
        // 雖然這種continue非常耗cpu,但一般碰不到這種情況,因為tail和head 在 TransferQueue 初始化時就已經被賦值空節點了
        if (t == null || h == null)
            continue;
-----------------------------------------------------------------------------------------------------------------                        
        // 情況一:首尾節點相同(空佇列)|| 尾節點的操作和當前節點操作一致(比如隊尾是take時阻塞,當前執行緒也是take)
        if (h == t || t.isData == isData) {
            QNode tn = t.next;
            // 當 t 不是 tail 時,即 tail 已經被修改過了,因為 tail 沒有被修改的情況下,t 和 tail 必然相等
            if (t != tail)
                continue;
            // 隊尾后面的值還不為空,t 還不是隊尾,直接把 tn 賦值給 t,這是一步加強校驗,
            if (tn != null) {
            	// CAS修改tail為tn
                advanceTail(t, tn);
                continue;
            }
            // 超時直接回傳 null
            if (timed && nanos <= 0)        // can't wait
                return null;
            // 構造node節點
            if (s == null)
                s = new QNode(e, isData);
            // 如果把 e 放到隊尾失敗,繼續遞回放進去
            if (!t.casNext(null, s))        // failed to link in
                continue;

            advanceTail(t, s);              // swing tail and wait
            // awaitFulfill 同 TransferStack,阻塞住自己,等待配對節點x
            Object x = awaitFulfill(s, e, timed, nanos);
            if (x == s) {                   // wait was cancelled
                clean(t, s);
                return null;
            }

            if (!s.isOffList()) {   // not already unlinked
            	// CAS修改head為s     
                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 {                            // complementary-mode
            // 如果是第一次執行,此處的 m 代表就是 tail
            // 也就是這行代碼體現出佇列的公平,每次操作時,從頭開始按照順序進行操作
            QNode m = h.next;               // node to fulfill
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read

            Object x = m.item;
            if (isData == (x != null) ||    // m already fulfilled
                x == m ||                   // m cancelled
                // m 代表堆疊頭
                // 這里把當前的操作值賦值給阻塞住的 m 的 item 屬性,所以 m 被釋放時,就可得到此次操作的值
                !m.casItem(x, e)) {         // lost CAS
                advanceHead(h, m);          // dequeue and retry
                continue;
            }
            // 當前操作放到隊頭
            advanceHead(h, m);              // successfully fulfilled
            // 釋放隊頭阻塞節點
            LockSupport.unpark(m.waiter);
            return (x != null) ? (E)x : e;
        }
    }
}

2.方法決議 & api

SynchronousQueue 的方法都很簡單,因為已經封裝好了兩種 Transfer 的實作,TransferStack 和 TransferQueue,所以后面的方法直接呼叫就行,

2.1 放入:put

將新元素放進佇列,直到有另外一個執行緒從佇列中取走,成功結束,失敗打斷執行緒

public void put(E e) throws InterruptedException {
    // e為空,拋例外
    if (e == null) throw new NullPointerException();
    // 呼叫transfer方法,傳入e
    // 一直等待
    if (transferer.transfer(e, false, 0) == null) {
        Thread.interrupted();
        throw new InterruptedException();
    }
}

2.2 取出:take

從佇列頭拿資料并洗掉資料,成功回傳,失敗打斷執行緒

public E take() throws InterruptedException {
    // 呼叫transfer方法,傳入null
    E e = transferer.transfer(null, false, 0);
    if (e != null)
        return e;
    Thread.interrupted();
    throw new InterruptedException();
}

2.3 容量相關方法

與容量相關方法都是默認實作,即寫死的,

peek()

public E peek() {
        return null;
}

remove()

public boolean remove(Object o) {
        return false;
}

contains()

public boolean contains(Object o) {
        return false;
}

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

標籤:其他

上一篇:ad域中巡檢統計資料用到的命令列

下一篇:微信公眾號禁用右上角分享

標籤雲
其他(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)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more