Java併發編程學習:線程安全與鎖優化

来源:https://www.cnblogs.com/Tom-shushu/archive/2018/06/07/9153313.html
-Advertisement-
Play Games

本文參考《深入理解java虛擬機第二版》 一。什麼是線程安全? 這裡我借《Java Concurrency In Practice》裡面的話:當多個線程訪問一個對象,如果不考慮這些線程在運行時環境下的調度和交替執行,也不需要額外的同步,或者調用方進行任何其他的協調操作,調用這個對象的行為都可以獲得正 ...


本文參考《深入理解java虛擬機第二版》

一。什麼是線程安全? 

  這裡我借《Java Concurrency In Practice》裡面的話:當多個線程訪問一個對象,如果不考慮這些線程在運行時環境下的調度和交替執行,也不需要額外的同步,或者調用方進行任何其他的協調操作,調用這個對象的行為都可以獲得正確的結果,那麼這個對象是線程安全的。

      我的理解:多線程訪問一個對象,任何情況下,都能保持正確行為,就是對象就是安全的。

 

   我們可以將Java語言中各種操作共用的數據分為以下5類:不可變、 絕對線程安全、 相對線程安全、 線程相容和線程對立。
1.不可變

   不可變(Immutable)的對象一定是線程安全的,無論是對象的方法實現還是方法的調用者,都不需要再採取任何的線程安全保障措施。
   Java語言中,如果共用數據是一個基本數據類型,那麼只要在定義時使用final關鍵字修飾它就可以保證它是不可變的。 如果共用數據是一個對象,那就需要保證對象的行為不會對其狀態產生任何影響才行,如果讀者還沒想明白這句話,不妨想一想java.lang.String類的對象,它是一個典型的不可變對象,我們調用它的substring()、 replace()和concat()這些方法都不會影響它原來的值,只會返回一個新構造的字元串對象。保證對象行為不影響自己狀態的途徑有很多種,其中最簡單的就是把對象中帶有狀態的變數都聲明為final,這樣在構造函數結束之後,它就是不可變的
  不可變的對象的,也就是被聲明成fianl的對象,只要被正確構建出來,在不發現this逃逸的情況下,其外部狀態永遠不會改變,永遠不會看到多個線程中處於不一致的狀態。也就是說所有對象的共用變數都聲明成final ,那麼就是安全的。

2.絕對線程安全

   絕對的線程安全完全滿足Brian Goetz給出的線程安全的定義,這個定義其實是很嚴格的,一個類要達到不管運行時環境如何,調用者都不需要任何額外的同步措施

   在Java API中標註自己是線程安全的類,大多數都不是絕對的線程安全。 
   如果說java.util.Vector是一個線程安全的容器,相信所有的Java程式員對此都不會有異議,因為它的add()、 get()和size()這類方法都是被synchronized修飾的,儘管這樣效率很低,但確實是安全的。 但是,即使它所有的方法都被修飾成同步,也不意味著調用它的時候永遠都不再需要同步手段了。如果一個線程要查找i位置的變數,結果另一個線程把他刪除了,就會包異常。

拋出異常的原因:因為如果另一個線程恰好在錯誤的時間里刪除了一個元素,導致序號i 已經不再可用的話,再用i 訪問數組就會拋出一個 ArrayIndexOutOfBoundsException

如果要保證這段代碼能夠正確執行下去,修改後的代碼為

 

// 對線程安全的容器 Vector的測試(修改後的代碼)
public class ModifiedVectorTest {
    private static Vector<Integer> vector = new Vector<>(); 
    
    public static void main(String[] args) {
        while(true) {
            for (int i = 0; i < 100; i++) {
                vector.add(i);
            }
            
            Thread removeThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (vector) { // 添加同步塊,this line
                        for (int i = 0; i < vector.size(); i++) {
                            vector.remove(i);
                        }
                    }
                }
            });
            
            Thread printThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (vector) { // 添加同步塊,this line
                        for (int i = 0; i < vector.size(); i++) {
                            System.out.println(vector.get(i));
                        }
                    }
                }
            });
            
            removeThread.start();
            printThread.start();
            
            // 不要同時產生過多的線程,否則會導致os 假死
            while(Thread.activeCount() > 20);
        }
    }
}

 

3.相對線程安全

   相對的線程安全就是我們通常意義上所講的線程安全,它需要保證對這個對象單獨的操作是線程安全的,我們在調用的時候不需要做額外的保措施,但是對於一些特定順序的連續調用,就可能需要在調用端使用額外的同步手段來保證調用的正確性。 
   在Java語言中,大部分的線程安全類都屬於這種類型,例如VectorHashTableCollectionssynchronizedCollection()方法包裝的集合等。

4.線程相容

   線程相容是指對象本身並不是線程安全的,但是可以通過在調用端正確地使用同步手段來保證對象在併發環境中可以安全地使用,我們平常說一個類不是線程安全的,絕大多數時候指的是這一種情況。 Java API中大部分的類都是屬於線程相容的,如與前面的VectorHashTable相對應的集合類ArrayListHashMap等。

5.線程對立

   線程對立是指無論調用端是否採取了同步措施,都無法在多線程環境中併發使用的代碼。 由於Java語言天生就具備多線程特性,線程對立這種排斥多線程的代碼是很少出現的,而且通常都是有害的,應當儘量避免。
   一個線程對立的例子是Thread類的suspend()和resume()方法,如果有兩個線程同時持有一個線程對象,一個嘗試去中斷線程,另一個嘗試去恢複線程,如果併發進行的話,無論調用時是否進行了同步,目標線程都是存在死鎖風險的,如果suspend()中斷的線程就是即將要執行resume()的那個線程,那就肯定要產生死鎖了。

線程安全的實現方法(備註:以下內容參考鏈接:https://www.cnblogs.com/pacoson/p/5351355.html) 1)互斥同步 1.1)互斥同步:是常見的併發正確性保障手段; 1.2)同步:是指在多個線程併發訪問共用數據時,保證共用數據在同一個時刻被一個線程使用。 1.3)互斥:互斥是實現同步的一種手段;臨界區,互斥量和信號量都是主要的互斥實現方式。因此,在這4個字裡面,互斥是因,同步是果;互斥是方法,同步是目的; 1.4)最基本的互斥同步手段就是 synchronized關鍵字:synchronized關鍵字經過 編譯之後,會在同步塊的前後分別形成 monitorenter 和 monitorexit 這個兩個位元組碼指令,這兩個位元組碼都需要一個 reference類型的參數來指明要鎖定和解鎖的對象;如果java程式中的synchronized明確指定了對象參數,那就是這個對象的reference;如果沒有明確指定,那就根據 synchronized修飾的實例方法還是類方法,去取對應的對象實例或Class 對象來作為鎖對象; 1.5)根據虛擬機規範的要求:在執行monitorenter指令時,如果這個對象沒有鎖定或當前線程已經擁有了那個對象的鎖,鎖的計數器加1,相應的,在執行 monitorexit 指令時會將鎖計數器減1;當計數器為0時,鎖就被釋放了; Attention)對於monitorenter 和 monitorexit 的行為描述中,有兩點需要註意: 01.synchronized同步塊對同一條線程來說是可重入的, 不會出現自己把自己鎖死的問題; 02.同步塊在已進入的線程執行完之前,會阻塞後面其他線程 的進入; 1.6)除了synchronized之外,還可以使用 java.util.concurrent 包中的重入鎖(ReentrantLock)來實現同步; 1.6.1)synchronized 和 ReentrantLock 的區別: 一個表現為 API 層面的互斥鎖(lock() 和 unlock() 方法配合 try/finally 語句塊來完成),另一個表現為 原生語法層面的互斥鎖; 1.6.2)ReentrantLock增加了一些高級功能:主要有3項:等待可中斷,可實現公平鎖, 以及鎖可以綁定多個條件;
case1)等待可中斷:指當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情,可中斷特性對處理執行時間非常長的同步塊很有幫助; case2)公平鎖:指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖; case3)鎖綁定多個條件:指一個 ReentrantLock對象可以同時綁定多個 Condition對象,而在 synchronized中,鎖對象的wait() 和 notify() 或 notifyAll() 方法可以實現一個隱含的條件,如果要和多於一個的條件關聯的時候,就不得不額外地添加一個鎖,而ReentrantLock 則無需這樣做,只需要多次調用 newCondition() 方法即可;

1.6.3)關於synchronized 和 ReentrantLock 性能的分析:

對上圖的分析(Analysis):

A1)多線程環境下 synchronized的吞吐量下降得非常嚴重,而 ReentrantLock 則能基本保持在同一個比較穩定的水平上;與其說ReentrantLock性能好,還不如說 synchronized還有非常大的優化餘地;

A2)虛擬機在未來的性能改進中肯定也會更加偏向於原生的 synchronized,所以還是提倡在 synchronized能實現需求的情況下,優先考慮使用 synchronized 來進行同步;

2)非阻塞同步 2.1)阻塞同步(互斥同步)的問題:就是進行線程阻塞和喚醒所帶來的性能問題,互斥同步屬於一種悲觀的併發策略,無論共用數據是否真的會出現競爭,它都要進行加鎖,用戶態核心態轉換,維護鎖計數器和檢查是否有被阻塞的線程需要喚醒等操作; 2.2)非阻塞同步定義:基於衝突檢測的樂觀併發策略,通俗的說,就是先進行操作,如果沒有其他線程爭用共用數據,那操作就成功了;如果共用數據有爭用,產生了衝突,那就再採用其他的補償措施,這種樂觀的併發策略的許多實現都不需要把線程掛起,因此這種同步操作稱為 非阻塞同步; 2.3)為什麼作者要說使用樂觀併發策略需要“硬體指令集的發展”才能進行呢?因為 我們需要操作和衝突檢測這兩個步驟具備原子性,靠什麼來保證呢? 2.3.1)硬體:保證一個從語義上看起來需要多次操作的行為只通過一次處理器指令就能完成,這類指令常用的有:(instructions) i1)測試並設置(Test-and-Set); i2)獲取並增加(Fetch-and-Increment); i3)交換(Swap); i4)比較並交換(Compare-and-Swap,下文簡稱 CAS); i5)載入鏈接/ 條件存儲(Load-Linked/Store-Conditional,下文簡稱 LL/SC);

2.4)如何使用CAS 操作來避免阻塞同步,看個荔枝:(測試incrementAndGet 方法的原子性)

複製代碼
// Atomic 變數自增運算測試(incrementAndGet 方法的原子性)
public class AtomicTest {
    public static AtomicInteger race = new AtomicInteger(0);
    
    public static void increase() {
        // 輸出正確結果,一切都要歸功於 incrementAndGet 方法的原子性
        race.incrementAndGet();  
    }
    
    public static final int THREADS_COUNT = 20;
    
    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        
        while(Thread.activeCount() > 1) {
            Thread.yield();
        }
        
        System.out.println(race);
    }
    
    /**
     * incrementAndGet() 方法的JDK 源碼
     * Atomically increment by one the current value.
     * @return the updated value
     */
    public final int incrementAndGet() {
        for(;;) {
            int current = get();
            int next = current + 1;
            if(compareAndSet(current,next)) {
                return next;
            }
        }
    }
}
複製代碼

2.5)CAS操作(比較並交換操作)的ABA問題:如果一個變數V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就說它的值沒有被其他線程改變過了嗎? 如果在這段期間它的值曾經被改為了B,之後又改回了A,那CAS操作就會誤認為它從來沒有被改變過,這個漏洞稱為 CAS操作的 ABA問題;

2.6)解決方法:J.U.C 包為瞭解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變數值的version 來保證CAS的正確性。不過目前來說這個類比較雞肋, 大部分cases 下 ABA問題 不會影響程式併發的正確性,如果需要解決ABA問題,改用傳統的互斥同步可能會比原子類更高效;

3)無同步方案 3.0)intro: 如果一個方法本來就不涉及共用數據,那它自然就無須任何同步措施去保證正確性,因此會有一些代碼天生就是線程安全的;下麵介紹兩類線程安全代碼: 3.1)第一類線程安全代碼——可重入代碼:也叫作純代碼,可以在代碼執行的任何時刻中斷它,轉而去執行另外一段代碼,而在控制權返回後,原來的程式不會出現任何錯誤; 3.1.1)所有的可重入代碼都是線程安全的; 3.1.2)如何判斷代碼是否具備可重入性:如果一個方法,它的返回結果是可以預測的,只要輸入了相同的數據,就都能返回相同的結果,那它就滿足可重入性的要求,當然也就是線程安全的; 3.2)第二類線程安全代碼——線程本地存儲:如果一段代碼中所需要的數據必須與其他代碼共用,那就看看這些共用數據的代碼是否能夠保證在同一線程中執行? 如果能保證,我們就可以把共用數據的可見範圍限制在同一個線程內,這樣,無需同步也可以保證線程間不出現數據爭用問題;
3。鎖優化 3.1 自旋鎖與自適應自旋 1)problem:前文中我們提到,互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢複線程的操作都需要轉入內核態中完成,共用數據的鎖定狀態只會持續很短的一段時間,為了這段時間去掛起和恢複線程很不值得; 2)自旋鎖定義:為了讓線程等待,我們只需讓線程執行一個忙迴圈(自旋),這項技術就是所謂的自旋鎖;(solution) 2.1)jdk1.6中 自旋鎖是預設開啟的,可以使用 -XX:+UseSpinning 參數來開啟; 2.2)自旋等待的時間必須要有一定的限度: 如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起線程了。自旋次數的預設值是10,用戶可以用參數 -XX:PreBlockSpin 來更改; 2.3)自適應自旋鎖:jdk1.6 中引入了自適應的自旋鎖。自適應意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定; case1)如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,並且持有鎖的線程正在運行中,那麼虛擬機就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,比如100個cycle; case2)如果對於某個鎖,自旋很少成功獲得過, 那在以後要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源;   3.2鎖消除 1)定義:鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢查到不可能存在共用數據競爭的鎖進行消除; 2)鎖消除的主要判定依據:來源於逃逸分析的數據支持;如果判定在一段代碼中,堆上的所有數據都不會逃逸出去從而被其他線程訪問到,那就可以把它們當做棧上數據對待,認為它們是線程私有的,同步加鎖自然就無須進行了; 3)problem+solution 3.1)problem:程式員自己應該很清楚,怎麼會在明知道不存在數據爭用的case下還要求同步呢? 3.2)solution:許多同步措施並不是程式員自己加入的,同步的代碼在java程式中的普遍程度早就超過了大部分人的想象; 3.3)這段code 僅僅是輸出3個字元串相加的結果,無論是源碼字面上還是程式語義上都沒有同步;

複製代碼
public class LockEliminateTest {
    
    // raw code
    public String concatString(String s1, String s2, String s3) {
        return s1 + s2 + s3;
    }
    
    // javac 轉化後的字元串連接操作
    public String concatString(String s1, String s2, String s3) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        return sb.toString();
    }
}
複製代碼

 對以上代碼的分析(Analysis):

A1)對於 javac 轉化後的字元串連接操作代碼: 使用了同步,因為StringBuffer.append() 方法中都有一個同步塊,鎖就是sb對象。虛擬機觀察變數sb,很快就會發現他的動態作用域被限制在 concatString() 方法內部;也就是所 sb 的所有引用都不會逃逸到方法之外;

A2)所以,雖然這裡有鎖,但是可以被安全地消除掉,在即時編譯之後,這段代碼就會忽略掉所有的同步而直接執行了;

3.3 鎖粗化 1)problem:如果一系列的連續操作都對同一個對象反覆加鎖和解鎖,甚至加鎖操作是出現在迴圈體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導致不必要的性能損耗; 2)鎖粗化的定義:如果虛擬機探測到有這樣一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的範圍擴展(粗化)到整個操作序列的外部; 3)以下麵的代碼為例,就是擴展到第一個 append() 操作前直到最後一個 append()操作之後,這樣只需要加鎖一次就可以了;  複製代碼
// javac 轉化後的字元串連接操作
    public String concatString(String s1, String s2, String s3) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        return sb.toString();
    }
複製代碼 3.4輕量級鎖 1)重量級鎖定義:使用操作系統互斥量來實現的傳統鎖; 2)輕量級鎖的目的:是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗; 3)HotSpot虛擬機的對象頭分為兩部分信息: 3.1)第一部分:用於存儲對象自身的運行時數據,如哈希碼,GC分代年齡等;這部分數據的長度在32位和64位的虛擬機中分別為 32bit 和 64bit,官方稱它為 Mark Word,它是實現輕量級鎖和偏向鎖的關鍵; 3.2)第二部分:用於存儲指向方法區對象類型數據的指針,如果是數組對象的話,還會有一個額外的部分用於存儲數組長度; 3.3)對象頭信息是與對象自身定義的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word 被設計成一個非固定的數據結構以便在極小的空間記憶體儲儘量多的信息,它會工具對象的狀態復用自己的存儲空間; 3.4)HotSpot 虛擬機對象頭Mark Word 如下圖所示:

4)在代碼進入同步塊的時候: 4.1)輕量級鎖的加鎖過程: step1)如果此同步對象沒有被鎖定(鎖標誌位為01狀態):虛擬機首先將在當前線程的棧幀中建立一個名為 鎖記錄的空間,用於存儲對象目前的Mark Word 的拷貝; step2)然後,虛擬機將使用CAS 操作嘗試將對象的 Mark Word 更新為指向 Lock Record的指針; step3)如果這個更新工作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標誌位將轉變為 00,即表示 此對象處於輕量級鎖定狀態; step4)如果這個更新失敗了,虛擬機首先會檢查對象的Mark Word 是否指向當前線程的棧幀,如果只說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明這個鎖對象以及被其他線程搶占了。如果有兩條以上的線程爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標誌的狀態值變為 10,Mark Word中存儲的就是指向重量級(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態; 4.2)輕量級鎖的解鎖過程: step1)如果對象的Mark Word仍然指向著線程的鎖記錄,那就用CAS 操作把對象當前的Mark Word 和 線程中複製的 Dispatched Mard Word替換回來; step2)如果替換成功,整個同步過程就over了; step3)如果替換失敗,說明有其他線程嘗試過獲取該鎖,那就要在釋放鎖的同時,喚醒被掛起的線程; Conclusion) C1)輕量級鎖能提升程式同步性能的依據是: 對於絕大部分的鎖,在整個同步周期內都是不存在競爭的; C2)如果沒有競爭,輕量級鎖使用CAS 操作避免了使用互斥量的開銷;但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS 操作,因此在有競爭的case下, 輕量級鎖會比傳統的重量級鎖更慢;   3.5,偏向鎖 1)偏向鎖的目的:消除數據在無競爭情況下的同步原語,進一步提高程式的運行性能; 2)如果說輕量級鎖是在無競爭的情況使用CAS 操作去消除同步使用的互斥量:那偏向鎖就是在無競爭的情況下把整個同步都消除掉,連CAS 操作都不做了 3)偏向鎖的偏: 它的意思是這個鎖會偏向於 第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步; 4)偏向鎖的原理:若當前虛擬機啟用了偏向鎖,那麼,當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標誌位設為01, 即偏向模式;同時使用CAS 操作把獲取到這個鎖的線程的ID 記錄在對象的 Mark Word之中,如果 CAS操作成功,持有偏向鎖的線程以後每次進入這個鎖相關的同步塊時,虛擬機都可以不再進行任何同步操作; 5)當有另一個線程去嘗試獲取這個鎖時,偏向模式就結束了:根據鎖對象目前是否處於被鎖定的狀態, 撤銷偏向後恢復到未鎖定(標誌位為01)或輕量級鎖定(標誌位為00)的狀態,後續的同步操作就如上面介紹的輕量級鎖那樣執行; Conclusion) C1)偏向鎖可以提高帶有同步但無競爭的程式性能; C2)如果程式中大多數的鎖總是被多個不同的線程訪問:那偏向模式是多餘的;
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 約定 還記得上版本的第二十四篇的約定嘛?現在出來履行啦~ 為什麼要重製? 之前寫的專欄都是按照心情寫的,在最初的時候筆者什麼都不懂,而且文章的發佈是按照很隨性的一個順序。結果就是說,大家都看完了,都還對框架沒有一個感覺,感覺很亂。而現在,經過兩年多的摸索,筆者已經對框架的體系有了一個瞭解,所以希望再 ...
  • Eclipse作為軟體開發的常用工具,被很多的人所歡迎,尤其是豐富的快捷鍵,可以極大的提高編碼的效率,下麵將常用的快捷鍵做了整理,便於大家學習和使用。 Eclipse常用快捷鍵 1代碼提示 Alt+/; 2代碼複製 ctrl+alt+方向鍵(上下) 複製快捷鍵 3單行註釋 方式一:ctr+/ 方式二 ...
  • 轉自:https://my.oschina.net/editorial-story/blog/1808757 本文是學習大型分散式網站架構的技術總結。對架構一個高性能、高可用、可伸縮及可擴展的分散式網站進行了概要性描述,並給出一個架構參考。文中一部分為讀書筆記,一部分是個人經驗總結,對大型分散式網站 ...
  • Java開源生鮮電商平臺-定時器,定時任務quartz的設計與架構(源碼可下載) 說明:任何業務有時候需要系統在某個定點的時刻執行某些任務,比如:凌晨2點統計昨天的報表,早上6點抽取用戶下單的佣金。 對於Java開源生鮮電商平臺而言,有定時推送客戶備貨,定時計算賣家今日的收益,定時提醒每日的提現金額 ...
  • 本文基於 jdk 1.8 。 CountDownLatch 的使用 "前面的文章" 中說到了 volatile 以及用 volatile 來實現自旋鎖,例如 java.util.concurrent.atomic 包下的工具類。但是 volatile 的使用場景畢竟有限,很多的情況下並不是適用,這個 ...
  • 譯者註:這是一篇很棒文章,使用有趣的敘述方式,從反面講解了作為一個優秀的 PHP 工程師,有哪些事情是你不能做的。請註意哦,此篇文章羅列的行為,都是你要儘量避免的。 隨著失業率越來越高,很多人意識到保全自己的工作是多麼的重要。那麼,什麼是保住自己工作,並讓自己無可替代的好方法呢?一個很簡單的事實是只 ...
  • 用 STS 創建 Maven 項目並不複雜,只是其中有一些坑在裡面,我在解決這些坑的時候發現很多人都遇到了相同的問題,因此把創建的步驟記錄在這裡。所有的步驟不外乎就是一些套路,並沒有什麼複雜的地方,只是在被套路的時候,找解決方法真是很煩人的一件事情。畢竟不是什麼大的技術問題,只是一個工具的使用,難道 ...
  • Java中的訪問控制修飾符一:Java修飾符的種類;二:訪問控制修飾符;1.預設訪問修飾符-不使用任何關鍵字;2.私有訪問修飾符-private;3.公有訪問修飾符-public;4.受保護的訪問修飾符-protected; ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...