帶你看看Java的鎖(一)-ReentrantLock

来源:https://www.cnblogs.com/burg-xun/archive/2020/04/05/12638786.html
-Advertisement-
Play Games

前言 AQS一共花了5篇文章,對裡面實現的核心源碼都做了註解 也和大家詳細描述了下,後面的幾篇文字我將和大家聊聊一下AQS的實際使用,主要會聊幾張鎖,第一篇我會和大家聊下ReentrantLock 重入鎖,雖然在講AQS中也穿插了講了一下,但是我還是深入的聊下 ==PS:前面5篇寫在了CSDN裡面 ...


前言

AQS一共花了5篇文章,對裡面實現的核心源碼都做了註解 也和大家詳細描述了下,後面的幾篇文字我將和大家聊聊一下AQS的實際使用,主要會聊幾張鎖,第一篇我會和大家聊下ReentrantLock 重入鎖,雖然在講AQS中也穿插了講了一下,但是我還是深入的聊下

PS:前面5篇寫在了CSDN裡面 懶得轉過來來了,有興趣的移步去看下吧

  1. Java-AQS同步器 源碼解讀1-獨占鎖加鎖
  2. Java-AQS同步器 源碼解讀2-獨占鎖解鎖
  3. Java-AQS同步器 源碼解讀3-共用鎖
  4. Java-AQS同步器 源碼解讀4-條件隊列上
  5. Java-AQS同步器 源碼解讀5-條件隊列下

ReentrantLock簡介

ReentrantLock中文翻譯:重入鎖。那具體重入是什麼意思呢,如果看過前面幾篇文章的人,應該瞭解一下,簡答的說就是AQS同步器裡面的State相當於一個計數器,如果某一個線程獲取鎖了以後再再次去獲取鎖,這個計算器State就會+1.後面的代碼中會詳細的描述到。
還有一個重要的點 就是lock和Condition的聯合使用,ReentrantLock可以創建一個Condition,這個我在條件隊列的文章中詳細描述過。

Synchronized對比

ReentrantLock是一個排他鎖,也就是同一個時刻只會有一個線程能獲取到鎖,這個主要利用的就是AQS的獨占模式實現的。ReentrantLock能保證在多線程的情況下的線程執行安全,那就會想到Synchronized的關鍵字。Synchronized是JVM實現的,ReentrantLock是由JDK實現的,ReentrantLock相對於比較靈活,可以設置時間等待,線程中斷,鎖投票等,但是一定用完記得要在finally手動釋放,Synchronized是JVM做自動釋放鎖的操作。

用法

/**
 * @ClassName ReentrantLockDemo
 * @Auther burgxun
 * @Description: 重入鎖的Demo
 * @Date 2020/4/5 14:21
 **/
public class ReentrantLockDemo {

    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                reentrantLock.lock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("獲取鎖的線程是:" + finalI);
                reentrantLock.unlock();
            }).start();
        }
    }
}

執行結果是:

org.example.ReentrantLockDemo
獲取鎖的線程是:1-開始執行
獲取鎖的線程是:1-執行結束
獲取鎖的線程是:2-開始執行
獲取鎖的線程是:2-執行結束
獲取鎖的線程是:4-開始執行
獲取鎖的線程是:4-執行結束
獲取鎖的線程是:3-開始執行
獲取鎖的線程是:3-執行結束
獲取鎖的線程是:5-開始執行
獲取鎖的線程是:5-執行結束

Process finished with exit code 0

從執行結果上 我們可以看到 一個線程執行完成釋放鎖後才能執行另外一個線程

源碼分析

看完了上面的簡介和用法,我們進入源碼去分析看下 是怎麼實現的

代碼結構

在這裡插入圖片描述

方法分析

從UML類圖上面我們可以看到 ReentrantLock有3個內部類,一個是抽象的靜態類Sync還有2個實現了Sync的類一個是非公平鎖的實現NonfairSync,還有一個是公平鎖的實現FairSync

Sync

首先我們看下Sync這個抽象類

abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();

        /**
         * 非公平鎖鎖的tryAcquire的實現 AQS中tryAcquire方法的需要子類重寫
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當前線程
            int c = getState();//獲取CAS的State值
            if (c == 0) {//如果等於0 說明可以獲取鎖
                if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 狀態
                    setExclusiveOwnerThread(current);//CAS修改成功後 設置當前線程為鎖的持有者
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {//判斷當前線程 是否是鎖的持有者線程
                int nextc = c + acquires;//新增重入次數
                if (nextc < 0) // overflow  //如果小於0 說明是出問題了 拋出異常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        /**
         * AQS 中方法的重寫 釋放資源
         */
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;// 算下 當前同步器中state的差值
            //確保釋放和加鎖線程是同一個  上一篇中我也提到過
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;//是否完全了釋放資源
            if (c == 0) {//如果C的值是0 說明沒有線程擁有鎖了
                free = true;
                /*
                 * 設置擁有鎖的線程為null 因為現在鎖是沒線程暫用的  如果不修改 下次別的線程去獲取鎖的會有這個判斷
                 */
                setExclusiveOwnerThread(null);
            }
            setState(c);//修改 同步器的State 狀態
            return free;
        }

        /**
         * 判斷當前線程 是否是擁有鎖的線程一致  重寫了AQS的方法
         */
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        /**
         * 獲取當前重入鎖的擁有者
         */
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        /**
         * 當前鎖占用的State 數量 也就是重入了幾次
         */
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        /**
         * 重入鎖 是否可以被占用  State等於0 別的線程才能來搶占到鎖
         */
        final boolean isLocked() {
            return getState() != 0;
        }
    }

上面是我寫了註解的Sync的整個類,一會兒我們挨個分析下,從繼承關係 我們可以看到Sync是繼承於我們的AQS的,所以裡面很多底層的方法都是用的AQS裡面的實現,所以說呀 理解了AQS 那整個JUC下的鎖和各種線程安全的集合什麼的 看源碼都會輕鬆很多~

NonfairSync

//非公平鎖
    static final class NonfairSync extends Sync {

        @Override
        void lock() {
            if (compareAndSetState(0, 1))//如果CAS能設置成功 說明能獲取到鎖 就設置當前線程為鎖的owner  不公平的體現
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        /**
         * 嘗試獲取資源,覆寫的AQS類裡面方法
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync

  //公平鎖
    static final class FairSync extends Sync {

        @Override
        void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                /*
                 * hasQueuedPredecessors 返回是否Sync中是否有在當前線程前面等待的線程 false沒有
                 * 如果false 那就CAS 修改State 成功後更新當前線程是鎖的擁有者線程
                 * */
                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;
        }
    }

非公平鎖VS公平鎖

什麼是公平非公平

看了上面的代碼 有人可能不明白什麼叫做TMD公平!!!哈哈
具體的代碼下文描述,我先舉個小例子,讓讀者帶入下:

話說大家在火車站排隊買票,小明和小強放寒假了,都去火車站提前買票準備回家,他們倆到了車站一看,售票視窗就開了一個,而且前面有3個人在排隊,那小明和小強沒辦法就只能排除在了隊伍裡面,等了5分鐘終於排到了小強,小強剛買完票,突然看到同個宿舍的小張也來買票了,立馬和小張說,來來來 這邊 我這邊可以買票,小張立馬插隊到了小強後面,強行去買了票,小明心裡嘀咕說居然插隊,素質真差,看你人高馬大的 就算了吧!哈哈~

其實上面的類子中 公平和非公平的體現就是 賣票的視窗只有一個,就像獲取獨占鎖,當有一個線程占有了鎖,那其餘的線程就必須在後面排隊等待,就像買票一樣,非公平的鎖的實現就相當於插隊,管你後面有沒有人 我都要去嘗試下買票

從上面的分析我們也能知道公平和非公平是指的是獲取資源時候的行為。

ReentrantLock

ReentrantLock的構造函數

/**
    * 預設實現非公平鎖
    */
   public ReentrantLock() {
       sync = new NonfairSync();
   }

   /**
    * 帶參數的構造函數
    */
   public ReentrantLock(boolean fair) {
       sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
   }

從上面可以看到ReentrantLock預設使用的是一個非公平鎖的實現

lock加鎖方法

    /**
     * 加鎖
     */
    @Override
    public void lock() {
        sync.lock();
    }

    /**
     * 加鎖 響應中斷
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    /**
     * 只做一次獲取鎖的嘗試  不會阻塞線程
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    @Override
    /**
     * 只做一次獲取鎖的嘗試  不會阻塞線程
     */
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

非公平的加鎖

非公平的加鎖

從代碼中 我們看到lock方法調用的是Sync的lock方法,但是Sync中的lock方法是一個抽象方式是子類實現的
那我從NonfairSync類中找到了lock的具體實現,入下:

 void lock() {
         //如果CAS能設置成功 說明能獲取到鎖 就設置當前線程為鎖的owner  不公平的體現
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

首先方法一開始先執行一個CAS修改State的操作,如果能夠執行成功,說明當前的AQS同步器中的狀態值是0,那麼線程就可以占有鎖,然後設置當前線程為鎖的Owner線程.setExclusiveOwnerThread這個方法我在第一篇文章中也描述過,這個方法是在AQS的父類AbstractOwnableSynchronizer方法裡面的!
如果執行失敗,那麼方法就執行acquire方法:

 /**
     * 此方法位於AQS中
     * 獲取資源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    /**
     * 此方法位於NonfairSync中
     * 嘗試獲取資源,覆寫的AQS類裡面方法
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
        
        
     /**
     * 此方法位於Sync中
     * 非公平鎖鎖的tryAcquire的實現 AQS中tryAcquire方法的需要子類重寫
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();//獲取當前線程
        int c = getState();//獲取CAS的State值
        if (c == 0) {//如果等於0 說明可以獲取鎖
            if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 狀態
                setExclusiveOwnerThread(current);//CAS修改成功後 設置當前線程為鎖的持有者
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {//判斷當前線程 是否是鎖的持有者線程
            int nextc = c + acquires;//新增重入次數
            if (nextc < 0) // overflow  //如果小於0 說明是出問題了 拋出異常
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

acquire方法位於AQS中 此方法會首先調用tryAcquire方法去嘗試獲取下鎖,因為雖然lock方法一開始獲取鎖失敗了,可能這邊鎖又被別的線程釋放了,所以要再次嘗試獲取下鎖,具體tryAcquire怎麼做的 上面的代碼中我已經描述的很清楚了~

公平的加鎖

公平的加鎖

先看下代碼:

     /**
     * 公平鎖版本的加鎖
     */
    void lock() {
        acquire(1);
    }

    /**
     * 此方法位於AQS中
     * 獲取資源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
        

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            /*
             * hasQueuedPredecessors 返回是否Sync中是否有在當前線程前面等待的線程 false沒有
             * 如果false 那就CAS 修改State 成功後更新當前線程是鎖的擁有者線程
             * */
            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;
    }

首先這邊lock的時候,直接執行了acquire方法,而非和NonfairSync一樣,有個CAS的嘗試操作,這也是體現公平的一部分,和非公鎖的方法一樣的是這邊的acquire方法也是在AQS上中的,但是調用的tryAcquire方法 是自己fairSync類自己實現的,而非調用的Sync的預設實現,這邊唯一有區別的就是hasQueuedPredecessors 這個方法,hasQueuedPredecessors方法是獲取當前Sync隊列中是否還有別的等待線程,如果有 就算當前的狀態State是滿足條件的,也是要加入Sync等待隊列中的,這個是acquireQueued方法裡面做的事情,不清楚這個acquireQueued方法的,看看前面幾篇文章,我就不再贅述了~

unlock解鎖

@Override
    public void unlock() {
        sync.release(1);//調用的AQS裡面的方法
    }
    
     /**
     * 此方法在AQS中
     * 釋放當前資源
     * 喚醒等待線程去獲取資源
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//說明釋放資源成功
            Node h = head;//當前隊列裡面的head節點
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//通知阻塞隊列裡面的head的next節點去獲取鎖
            return true;
        }
        return false;
    }
    
    
     /**
     * 此方法在Sync中
     * AQS 中方法的重寫 釋放資源  
     */
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;// 算下 當前同步器中state的差值
        //確保釋放和加鎖線程是同一個  上一篇中我也提到過
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;//是否完全了釋放資源
        if (c == 0) {//如果C的值是0 說明沒有線程擁有鎖了
            free = true;
            /*
             * 設置擁有鎖的線程為null 因為現在鎖是沒線程暫用的  如果不修改 下次別的線程去獲取鎖的會有這個判斷
             */
            setExclusiveOwnerThread(null);
        }
        setState(c);//修改 同步器的State 狀態
        return free;
    }
    

從源碼上看 解鎖的方法只有一個 因為公平鎖和非公共鎖 只是描述的是加鎖的行為,解鎖的行為其實都是一致的,都是釋放當前線程占用State值,然後喚醒SyncQueue的頭部Head節點的下一個節點去嘗試獲取鎖!

有些沒帖子上面的方法描述 請在前面AQS中的幾篇文章中看下 都詳細描述過~

總結

公平鎖 VS 非公平鎖

首先2者並沒有好壞之分,是要根據對應的場景選擇對應的鎖技術

公平鎖 則重的是公平性

非公平鎖 則重的是併發性

非公平鎖 是搶占式的,忽略了SyncQueue重其他的等待線程,線程在進入等待隊列之前會進行2次嘗試獲取鎖,這大大增加了獲取鎖的機會,這種好處體現在2個方面:

  • 線程不必加入等待隊列就可以獲取鎖,不僅免去了構造節點並加入隊列的繁瑣操作,同時也節省了線程阻塞的喚醒開銷,線程阻塞和喚醒涉及到線程上下文的切換和操作系統的系統調用,是非常耗時的。在高併發的情況下,如果線程持有鎖的時間非常短,短到線程入隊阻塞的過程超過了線程持有並釋放鎖的時間的開銷,那麼這種搶占式的特性對併發的性能的提高會很明顯
  • 減少CAS競爭,如果線程必須要加入阻塞隊列才能去獲取鎖,那麼入隊時的CAS競爭將變得異常的激烈,CAS操作雖然不會導致線程失敗而掛起,但不斷的失敗重試導致對CPU的浪費是不能忽略的

Synchronized VS ReentrantLock

從整個文章的分析來看,ReentrantLock是比Synchronized更加的靈活的,

  • ReentrantLock提供了更多 更全面的API 可以設置等待時間,可以中斷方法等,還提供了Trylock等非阻塞的方法
  • ReentrantLock還可以配和Condition一起使用,使得線程等待的時候更加靈活,可以設置不同的條件等待 等等

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

-Advertisement-
Play Games
更多相關文章
  • 最近c艹在學類與對象 擅長划水的我並沒有聽課,害,所以東西有點雲里霧裡, 所以寫下這一篇博客來理解一下。 類的使用就像是封裝一類的東西,定義為一個類 定義的地方和結構體其實還是有點相似的。 這東西在演算法裡面用的感覺不多, 以前也沒接觸過, 現在發現實際運用還是很有意思的。 首先是定義 class C ...
  • Set集合的功能和Collection是一致的。 HashSet:底層數據結構是哈希表,線程非同步。 HashSet保證元素唯一性:hashCode()和equals() 如果元素的hashCode值相同,才會判斷equals是否為true。 如果元素的hashCode值不同,不會調用equals。 ...
  • from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba # 生成詞雲 def create_word_cloud(filename): with open('hongloumong.txt',encod ...
  • 為什麼要寫STL淺談這個系列,因為最近我在準備藍橋杯,刷題的時候經常要用到STL,準備補一補,但一直沒有找到一個好的視頻和資料,最開始準備跟著c語言中文網學,但覺得太繁雜了,最後在b站(b站上電腦類的教學視頻挺多的)上找了一個視頻學的。這個系列相當於我的一個整理。 這個系列只是淺談,但刷題應該夠了 ...
  • 這是我的第一篇隨筆,當然是發我寫的第一個游戲啦! 大一(本人現在大二)寒假過完年,在家待著想起放假前計劃寫一個游戲的,因為本人立志走游戲開發這條路,加上大一上冊學了C語言,就想寫個游戲練練手。想了很久,最後決定寫一個俄羅斯方塊。 萬事開頭難,一開始真的不知道從何下手,所以百度查了一些資料(程式猿必須 ...
  • 1. web.xml中url pattern配置 url pattern為/ 使用SpringMVC的框架時,需要在 配置前端控制器 ,配置為: url pattern為/ 我們需要使用過濾器時,需要在 中進行註冊。以處理中文亂碼的過濾器 為例,配置為: url pattern為 .do 使用Str ...
  • [TOC] pandas可以進行數據輸入和輸出,有以下幾種類型:讀取文本文件及硬碟上其他更高效的格式文件,從資料庫中載入數據,於網路資源進行交互(比如Web API)。 下麵進行不同文本文件的讀取和寫入操作講解,首先進行文本格式數據的讀寫講解。 一:文本格式數據的讀寫 將表格型數據讀取為DataFr ...
  • 原文: "Java Garbage Collection Algorithms [till Java 9]" 垃圾回收(Garbage collection,GC)一直是 Java 流行背後的重要特性之一。垃圾回收是 Java 中用於釋放未使用記憶體的機制。本質上,它跟蹤所有仍在使用的對象,並將其餘的 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...