JUC源碼學習筆記1——AQS和ReentrantLock

来源:https://www.cnblogs.com/cuzzz/archive/2022/07/10/16463588.html
-Advertisement-
Play Games

筆記主要參考《Java併發編程的藝術》並且基於JDK1.8的源碼進行的刨析,此篇只分析獨占模式,後續在ReentrantReadWriteLock和 CountDownLatch中 會重點分析AQS的共用模式 一丶Lock 鎖是用來控制多個線程訪問共用資源的方式,一般來說,一個鎖可以防止多個線程同時 ...


筆記主要參考《Java併發編程的藝術》並且基於JDK1.8的源碼進行的刨析,此篇只分析獨占模式,後續在ReentrantReadWriteLock和 CountDownLatch中 會重點分析AQS的共用模式

一丶Lock

鎖是用來控制多個線程訪問共用資源的方式,一般來說,一個鎖可以防止多個線程同時訪問共用資源(這種鎖稱為獨占鎖,排他鎖)但是有些鎖可以允許多個線程併發訪問共用資源,比如讀寫鎖

1.Lock介面的方法:

方法 作用
void lock() 獲取鎖,調用該方法的線程將會獲取鎖,當鎖獲得之後從該方法返回
void lockInterruptibly() 可中斷地獲取鎖,該方法會響應中斷,在鎖的獲取可以中斷當前線程,如果在獲取鎖之前設置了中斷標誌,or獲取鎖的中途被中斷or其他線程中斷該線程則拋出InterruptedException並清除當前線程的中斷狀
boolean tryLock() 嘗試非阻塞的獲取鎖,調用方法會立即返回,如果可以獲取到鎖返回true
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 超時獲取鎖,從當前返回有三種情況
1.超時時間內獲取到鎖
2.當前線程在超時時間內被中斷3.超時間結束沒有獲取到鎖,返回false
void unLock 釋放鎖
Condition newCondition() 獲取等待通知的組件,該組件和當前鎖綁定,只有獲取到鎖調用wait方法後當前線程將放棄鎖,後續被其他線程signal繼續爭搶鎖

2.Lock相比synchronized具備的特性

  • 嘗試非阻塞的獲取鎖
  • 響應中斷的獲取鎖
  • 超時獲取鎖
synchronized相比於Lock 更加簡單,更不容易犯錯,但是不夠靈活

3.使用Lock的經典範式

獲取鎖的過程不要寫在try中,避免獲取鎖失敗最後finally釋放其他線程持有的鎖

二丶AbstractQueuedSynchronizer隊列同步器

使用一個int成員變數state表示同步狀態,內置的FIFO隊列來完成資源的獲取和線程的排隊工作,支持獨占也支持共用的獲取同步狀態。

三個變數被volatile修飾,保證其線程可見性

1.隊列同步器可以被重寫的方法

方法 說明
protected boolean tryAcquire(int arg) 獨占的獲取鎖,需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS改變同步狀態
protected boolean tryRelease(int arg) 獨占式釋放同步狀態,等待獲取同步狀態的線程將有機會獲取同步狀態
protected int tryAcquireShared(int arg) 共用式的獲取同步狀態,放回大於等於0()的值表示成功,反之失敗
protected boolean tryReleaseShared(int arg) 共用式釋放同步狀態
protected boolean isHeldExclusively() 當前隊列同步器釋放再獨占模式下被線程占用,一般表示當前線程是否獨占

2.隊列同步器提供的模板方法

方法 說明
void acquire(int arg) 獨占式獲取同步狀態,如果獲取成功那麼直接返回,反之進入同步隊列中等待,
void acquireInterruptibly(int arg) 和acquire,但是此方法支持在獲取鎖的過程中響應中斷,如果當前線程被中斷那麼拋出InterruptedException
boolean tryAcquireNanos(int arg, long nanosTimeout) 在acquireInterruptibly的基礎上增加了超時限制,如果在指定時間內沒有獲取到同步狀態那麼返回false反之true
void acquireShared(int arg) 共用式獲取同步狀態,如果沒有獲取到那麼進入等待隊列等待,和acquire不同的式支持同一個時刻多個線程獲取同步狀態
void acquireSharedInterruptibly(int arg) 和acquireShared類似但是支持響應中斷
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) 在acquireSharedInterruptibly新增了超時限制
boolean release(int arg) 獨占式釋放同步資源,在釋放同步狀態後喚醒後繼線程
boolean releaseShared(int arg) 共用式釋放同步狀態
Collection<Thread> getQueuedThreads() 獲取等待在同步隊列上的線程們

3.同步隊列的節點屬性

屬性 描述
int waitStatus 等待狀態
Node pre 前驅節點
Node next 後繼節點
Node nextWaiter 等待隊列中的後繼節點,如果當前節點式共用模式,那麼這個節點是SHARED常量,也就是說節點類型和等待中後繼節點是公用一個欄位
Thread thread 獲取同步狀態的線程

等待狀態是一個枚舉,具備下列可選的值

  • CANCELLED(1)線程獲取鎖超時or被中斷,需要從同步隊列中取消中斷,節點進入改狀態後狀態不再改變
  • SIGNAL(-1)後繼節點線程處於等待狀態,而當前節點的線程如果釋放共用資源或者被取消會通知後繼節點,使後繼線程被喚醒繼續執行
  • CONDITION(-2)節點在等待隊列中,節點中的線程在Condition上進行等待,需要等待其他線程調用Condition的singal or singalAll進行喚醒,該節點會從等待隊列移動到同步隊列,進行共用資源的爭奪
  • PROPAGATE(-3)表示下一次共用式同步狀態的獲取將無條件的傳播下去
  • 0 初始狀態,節點假如到同步隊列時候的狀態

4.AQS怎麼維護同步隊列

AQS中包含兩個節點類型引用:頭節點和尾節點。當一個線程獲取到同步狀態的時候,其他線程無法獲取,將被放入到同步隊列中,加入隊列這個過程為了保證線程安全而採用CAS。同步隊列遵守FIFO,頭節點是獲取到同步狀態的線程,釋放同步狀態將會喚醒後繼線程,後繼節點獲取到同步狀態後將被設置為頭節點

三丶ReentrantLock可重入鎖

1.ReentrantLock簡介

支持公平和非公平和重入的獨占式鎖

  • 重入表示已經獲得鎖的線程可以對共用資源重覆加鎖
  • 公平鎖,支持先來後到,像在公司排隊上廁所,先來的人肯定優先獲取到茅坑,先來的線程肯定先獲取到共用資源
  • 獨占式,同一時間只允許一個線程操作共用資源

2.公平鎖和非公平鎖比較

公平鎖在頭節點釋放同步資源的時候需要unpark後續節點,並切換線程執行上下文,導致效率並不如非公平鎖,但是公平鎖可以減少饑餓,因為非公平鎖好像A在排隊,A獲取到共用資源需要進行喚醒和上下文切換,而導致需要更多時間,這時候流氓B剛好進廁所門,上來就是一個CAS,很快搶占了廁所這一共用資源,導致A處於饑餓——遲遲得不到廁所(共用資源)的操作資源。

3.ReentrantLock的可重入

實現可重入需要解決兩個問題

  • 線程再次獲得鎖,鎖需要去識別當前線程釋放是當前占據鎖的線程,如果是那麼直接加鎖成功
  • 鎖的最終釋放,加鎖多少次,就需要釋放多少次,完全解鎖後其他線程才可以獲取到鎖。

第一個問題ReentrantLock通過獲取當前線程和獨占鎖線程的`==1判斷來實現,第二個問題ReentrantLock通過對AQS中的共用資源state增加和減少來實現

四丶結合ReentrantLock分析加鎖解鎖的流程

1.ReentranLock

ReentrantLock的公平和非公平就是由於sync引用指向了的不同實現,其lock unlock等操作也是一律交由到sync

2.ReentrantLock的非公平模式

2.1非公平加鎖——lock方法

加鎖的大致流程

  • 無論是非公平還是公平在加鎖成功後都會通過setExclusiveOwnerThread設置當前線程為獨占鎖的線程,這個方法會記住當前線程,這是後面實現可重入的關鍵、
  • acquire 方法會調用tryAcquire方法,這個方法由AQS的子類實現,NonfairSync這裡會調用nonfairTryAcquire方法
2.1.1不公平的嘗試獲取共用資源nonfairTryAcquire

  • 如果nonfairTryAcquire返回true表示當前線程獲取到了鎖,那麼皆大歡喜,當前線程可以繼續運行

  • 返回false的情況

    • 共用資源是0,但是同一個時間多個線程搶占,當前這個線程CAS失敗了
    • 共用資源不是0,當前線程也不是獨占的線程

    這兩種情況都需要繼續執行AQS的acquire方法

2.1.2AQS的acquire 方法

獨占模式獲取共用資源,對中斷不敏感,或者說不響應中斷——獲取共用資源失敗的線程將會進入到同步隊列,後續對此線程進行中斷操作,線程不會從同步隊列中移出

1.執行流程

2.將當前線程包裝成Node加入到隊列尾addWaiter

  • 快速入隊

    下麵這段代碼值得品一品

    Node pred = tail;
    if (pred != null) {
    	//當前線程的前置設置為尾,這一步那麼多個線程執行這一步也是無關緊要的
        //只是把當前節點的前置改變了,不是改變pred的next指向
        node.prev = pred;
        //CAS設置尾節點 為當前節點,這個自選操作compareAndSetTail是線程安全,同一時間只有一個線程可以設置自己為尾節點
        if (compareAndSetTail(pred, node)) {
            //註意 如果原尾節點是S,線程A設置成功 那麼尾巴被修改為了A,假如A執行下麵一行的時候消耗完了時間片,線程B進來了,這時候線程B拿到的tail就是A,所以不會存線上程安全問題
            pred.next = node;
            return node;
        }
    }
    
  • 完整入隊

    !

    完整入隊和快速入隊差不多,就是多了一個初始化的邏輯

    那麼為什麼不直接完整入隊,也許是for迴圈比if多更多的位元組碼需要執行?也許Doug Lea測試多次後發現快速入隊後完整入隊,比直接完整入隊效率更高

3.嘗試出隊acquireQueued

  • 如何從自旋中退出

    前繼節點是頭節點,頭節點是當前獲取到共用資源的節點,且獲取共用資源tryAcquire成功

  • 掛起當前線程避免無休止的自選

    自選是cpu操作,無限制的自選是很浪費cpu資源的

如果shouldParkAfterFailedAcquire放回true 表示當前線程需要被掛起,會繼續執行parkAndCheckInterrupt,這個方法很簡單隻有兩行

private final boolean parkAndCheckInterrupt() {
 	//掛起當前線程
    LockSupport.park(this);
    
    //返回中斷狀態,並且清除中斷標識
    return Thread.interrupted();
}

如果parkAndCheckInterrupt 返回了true 表示當前線程被中斷過,並且會讓外層的acquireQueued返回true,會導致acquire執行當前線程的自我中斷

理解這一段代碼需要對java中斷機制具備一定理解

java線程中斷機制
  • 調用Thread的interrupt方法

    • 如果線程處於Running狀態那麼只是修改Thread內部的中斷標識值為true
    • 如果線程由於sleep,wait,join等方法進入等待狀態,會直接拋出中斷異常並清楚中斷標識
    • 如果線程由於LockSupport.park進入等待狀態,調用該線程的interrupt方法只會讓LockSupport.park返回
  • interrupt,interrupted,isInterrupted三個方法比較

    • interrupt 見上⬆

    • interrupted 返回當前線程的中斷標識並且充值中斷標識

    • isInterrupted返回中斷標識

我們繼續說為什麼當前線程在獲取鎖的途中被中斷,需要自我中斷以下

acquire的"需求":
獨占模式獲取共用資源,對中斷不敏感,或者說不響應中斷——獲取共用資源失敗的線程將會進入到同步隊列,後續對此線程進行中斷操作,線程不會從同步隊列中移出

線程獲取同步狀態的時候被中斷會發生什麼——從LockSupport.park(this)中返回繼續拿鎖,這就是為什麼說acquire的對中斷不敏感。

LockSupport.park();不會拋出受檢查異常,當出現被打斷的情況下,線程被喚醒後,我們可以通過Interrupt的狀態來判斷,我們的線程是不是被interrupt的還是被unpark或者到達指定休眠時間

假如我們寫如下這樣的代碼執行

存在一個調度線程中斷了上面的線程,但是上面的線程還在搶奪鎖,並且被park了,這時候上麵線程的park會返回,並且清除中斷標識,如果不進行自我中斷,那麼下麵while內容還是會進行,那麼我們調度線程的中斷就無效了

3.ReentrantLock的公平模式

傳入true獲取一個公平鎖

3.1公平的獲取鎖——lock方法

公平鎖的lock方法直接調用AQS的acquire方法,上面我們分析的acquire方法它會先去調用tryAcquire,這個tryAcquire被FairSync重寫

  • FairSync的tryAcquire方法
 protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
     //共用狀態當前空閑
        if (c == 0) {
            //前面沒有節點 這就是公平是怎麼實現的
            //且cas成功 那麼拿到鎖
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
     //實現重入 和 公平鎖一樣
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

源碼沒什麼很難的點,就是通過判斷前面時候還有節點(標識是否由線程比當前線程先到)如果沒有那麼再去拿鎖,如果共用狀態不是0且當前線程不是獨占的線程那麼就會執行acquireQueued方法,在acquireQueued裡面自選獲取鎖會判斷前一個節點是否是頭節點且調用tryAcquire

4.釋放鎖

釋放鎖直接調用AQS的release方法,其中tryRelease方法由ReentrantLock中Sync自己實現(公平or非公平都一樣)

public final boolean release(int arg) {
    //完全的釋放資源
    if (tryRelease(arg)) {
        Node h = head;
        //頭節點初始化的時候才為0,但是後面如果由節點加入到同步隊列會把前置節點的狀態設置為Singnal
        if (h != null && h.waitStatus != 0)
            //喚醒後繼節點
            unparkSuccessor(h);
        return true;
    }
    return false;
}

4.1 tryRelease

protected final boolean tryRelease(int releases) {
	//重入了n次,當前釋放m次 c=n-m
    int c = getState() - releases;
    //如果不是獨占鎖的線程 那麼拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
	//是否完全的釋放了鎖
    boolean free = false;
	//只有剩下的為0 才是完全釋放鎖
    if (c == 0) {
        //置為true
        free = true;
		//獨占線程設置為null
        setExclusiveOwnerThread(null);
    }
    //修改state
    setState(c);
    return free;
}

需要註意的是只有完全的釋放了共用資源,在ReentrantLock里就是加鎖n次解鎖n次,才返回true,才會去喚醒後繼節點

4.2 unparkSuccessor

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //從尾巴開始找到隊列最前面的且需要通知的節點 為什麼要從尾巴開始找?
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)//大於0代表放棄了
                s = t;
    }
    if (s != null)
 		//喚醒
        LockSupport.unpark(s.thread);
}

使用 LockSupport.unpark(s.thread)喚醒線程,這裡需要品一品 Doug Lea 他為什麼要從尾部開始喚醒

  • 再品入隊

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //快速入隊要求尾節點不為空
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    
    • 快速入隊

      快速入隊要求尾節點不為空,如果尾節點為空那麼說明

      1. 當前沒有線程競爭,鎖只有一個線程再使用,直接tryAcquire就成功了,所以頭和尾都沒有初始化
    • 完整入隊

      • 進入完成入隊條件
        • 隊列頭和尾沒有初始化
        • CAS失敗,也就是說存在比較多的線程在執行快速入隊
      private Node enq(final Node node) {
          for (;;) {
              Node t = tail;
              if (t == null) { // Must initialize
                  if (compareAndSetHead(new Node()))
                      tail = head;
              } else {
           		//假設AB兩個線程現在正在搶鎖
                  node.prev = t;
                  //CAS設置尾 當前線程A被設置為了尾
                  if (compareAndSetTail(t, node)) {
                      //假如A執行這一行的時候用完了時間片,輪到了B B把自己設置了尾並且B的前置是A,此時A的前置還沒來得及設置
                    
                      //如果這個時候進行喚醒,從頭開始遍歷的話會發現沒有後面的節點了
                      //所以需要從尾開始,找到B,B繼續往前找到A
                      //Doug Lea 永遠的神
                      t.next = node;
                      return t;
                  }
              }
          }
      }
      
  • 為什麼要從尾開始遍歷

5.其他

5.1獨占式嘗試獲取鎖—— tryLock方法

這部分都是調用的nonfairTryAcquire方法,也就是是說無論是公平還是非公平都是直接不公平的獲取資源。tryLock方法是直接嘗試,只有當前共用資源沒有被占用的時候返回true,否則false 並且是立即返回所以無論是公平還是非公平,調用這個方法都是一樣的邏輯——有人占著廁所那就直接回去繼續工作

5.2 獨占式響應中斷的獲取鎖——lockInterruptibly

這個方法直接調用了AQS的acquireInterruptibly(1)

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
 	//如果已經中斷了那麼拋出中斷異常
    //Thread.interrupted() 會清除中斷標識,因為拋出InterruptedException就是響應了中斷,
    if (Thread.interrupted())
        throw new InterruptedException();
    //調用公平or非公平自己覆寫的方法 
    if (!tryAcquire(arg))
        //如果嘗試獲取共用資源失敗了  那麼入隊,自旋的共用資源
        doAcquireInterruptibly(arg);
}
5.2.1 doAcquireInterruptibly

基本上和acquireQueued差不多,就是自旋時發現中斷了那麼拋出中斷異常,註意parkAndCheckInterrupt是調用的Thread.interrupted(),會清除中斷標識

個人認為
  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //不是interrupted 而是 isInterrupted
        return Thread.isInterrupted();
    }
也可以實現功能,無非使用的地方比如doAcquireInterruptibly  要先調用Thread.interrupted()然後拋出異常
並且acquire方法也不要執行自我中斷
5.2.2 cancelAcquire

放棄共用資源的爭搶,一般是等待超時,或者被中斷後響應中斷

總體上就是,如果當前節點前面右節點可以喚醒當前節點的後繼節點,那麼CAS設置,否則直接喚醒後面的節點,並且把自己從隊列移除

5.3超時並響應中斷的獲取鎖——tryLock(long timeout, TimeUnit unit)

直接調用了AQS的tryAcquireNanos方法

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    //上來就判斷下是否中斷了
    if (Thread.interrupted())
        throw new InterruptedException();
	//嘗試獲取鎖 (調用對應公平和非公平的方法)or doAcquireNanos
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}
5.3.1doAcquireNanos

大致邏輯還是那些,需要註意的是,nanosTimeout > spinForTimeoutThreshold,剩餘時間大於閾值(1000)才會掛起,如果小於的化還是進行自旋,因為非常短的超時時間無法做到十分精確(掛起和喚醒也是需要時間的)如果還是進行超時等待反而會表現得不精確


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python設計模式-六大設計原則 單一職責原則 (Single Responsibility Principle) 顧名思義,單一職責的原則是說一個類只負責一項職責(操作)。如果一個類負責多個職責,其中一項職責發生變化就需要修改整個類,這可能會導致其他的職責運行錯誤。一個類,只應該有一個引起它變化 ...
  • Python設計模式-結構型:適配器模式,裝飾者模式,代理模式,組合模式,外觀模式 適配器模式定義及簡單實現案例 裝飾者模式定義及簡單實現案例 代理模式定義及簡單實現案例 組合模式定義及簡單實現案例 外觀模式定義及簡單實現案例 適配器模式 adapter 電子產品的電源插頭插在轉換插頭上,然後轉換插 ...
  • Python設計模式-行為型:策略模式,觀察者模式,命令模式,模板方法 行為型模式會涉及到演算法和對象間的職責分配,不僅描述對象或類的模式,還描述它們之間的通信方式,刻划了運行時難以跟蹤的複雜的控制流,它們將你的註意力從控制流轉移到對象間的關係上來。 策略模式定義及簡單實現案例 觀察者模式定義及簡單實 ...
  • 1.Static 詳情見下麵代碼講解 點擊查看代碼 package com.Tang.oop.demo07; public class Student { private static int age;//靜態變數 private double score;//非靜態變數 public void r ...
  • 本文節選左耳朵耗子相關文章,與讀者共勉! 本質上來說,程式員是手藝人,有手藝的人就能做出別人做不出來的東西,而付費也是一件很自然的事了。那麼,這個問題就成了,如何讓自己的“手藝”更為值錢的問題了。 千里之行,積於跬步 任何一件成功的大事,都是通過一個一個的小成功達到的。所以,你得確保你有一個一個的小 ...
  • django 啟動關閉和基礎文件說明 創建一個項目 成功安裝 django 之後,我們的終端會多出一個叫 django-admin的命令,我們可以使用這個命令來創建我們新的項目 我們可以在命令行輸入下列命令來創建一個新的項目,內部包含一個基礎網頁以及框架的相關內容 # 格式 django-admin ...
  • 帶你系統學習並且自己動手寫一個自己的哈希表,從哈希表的整體設計,再到細節哈希函數、哈希衝突和擴容設計,內容精彩至極!!! ...
  • 1.Instanceof作用 用來判斷兩個兩個類之間是否存在父子關係 代碼及詳解如下: Application類代碼 點擊查看代碼 package com.Tang.oop.demo06; public class Application { public static void main(Stri ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...