萬字詳解 Java 線程安全,面試必備!

来源:https://www.cnblogs.com/javastack/archive/2022/11/21/16911944.html
-Advertisement-
Play Games

來源:blog.csdn.net/u014454538/article/details/98515807 1. Java中的線程安全 Java線程安全:狹義地認為是多線程之間共用數據的訪問。 Java語言中各種操作共用的數據有5種類型:不可變、絕對線程安全、相對線程安全、線程相容、線程獨立 ① 不可 ...


來源:blog.csdn.net/u014454538/article/details/98515807

1. Java中的線程安全

  • Java線程安全:狹義地認為是多線程之間共用數據的訪問。
  • Java語言中各種操作共用的數據有5種類型:不可變、絕對線程安全、相對線程安全、線程相容、線程獨立

① 不可變

  • 不可變(Immutable) 的對象一定是線程安全的,不需要再採取任何的線程安全保障措施。
  • 只要能正確構建一個不可變對象,該對象永遠不會在多個線程之間出現不一致的狀態。
  • 多線程環境下,應當儘量使對象成為不可變,來滿足線程安全。

如何實現不可變?

  • 如果共用數據是基本數據類型,使用final關鍵字對其進行修飾,就可以保證它是不可變的。
  • 如果共用數據是一個對象,要保證對象的行為不會對其狀態產生任何影響。
  • String是不可變的,對其進行substring()、replace()、concat()等操作,返回的是新的String對象,原始的String對象的值不受影響。而如果對StringBuffer或者StringBuilder對象進行substring()、replace()、append()等操作,直接對原對象的值進行改變。
  • 要構建不可變對象,需要將內部狀態變數定義為final類型。如java.lang.Integer類中將value定義為final類型。

Java 面試題最全整理:https://www.javastack.cn/mst/

private final int value;

常見的不可變的類型:

  • final關鍵字修飾的基本數據類型
  • 枚舉類型、String類型
  • 常見的包裝類型:Short、Integer、Long、Float、Double、Byte、Character等
  • 大數據類型:BigInteger、BigDecimal

註意:原子類 AtomicInteger 和 AtomicLong 則是可變的。

對於集合類型,可以使用 Collections.unmodifiableXXX() 方法來獲取一個不可變的集合。

  • 通過Collections.unmodifiableMap(map)獲的一個不可變的Map類型。
  • Collections.unmodifiableXXX() 先對原始的集合進行拷貝,需要對集合進行修改的方法都直接拋出異常。

例如,如果獲得的不可變map對象進行put()、remove()、clear()操作,則會拋出UnsupportedOperationException異常。

② 絕對線程安全

絕對線程安全的實現,通常需要付出很大的、甚至不切實際的代價。

Java API中提供的線程安全,大多數都不是絕對線程安全。

例如,對於數組集合Vector的操作,如get()、add()、remove()都是有synchronized關鍵字修飾。有時調用時也需要手動添加同步手段,保證多線程的安全。

下麵的代碼看似不需要同步,實際運行過程中會報錯。

import java.util.Vector;

/**
 * @Author: lucy
 * @Version 1.0
 */
public class VectorTest {
    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();
        while(true){
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println("獲取vector的第" + i + "個元素: " + vector.get(i));
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<vector.size();i++){
                        System.out.println("刪除vector中的第" + i+"個元素");
                        vector.remove(i);
                    }
                }
            }).start();
            while (Thread.activeCount()>20)
                return;
        }
    }
}

出現ArrayIndexOutOfBoundsException異常,原因:某個線程恰好刪除了元素i,使得當前線程無法訪問元素i。

Exception in thread "Thread-1109" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1
 at java.util.Vector.remove(Vector.java:831)
 at VectorTest$2.run(VectorTest.java:28)
 at java.lang.Thread.run(Thread.java:745)

需要將對元素的get和remove構造成同步代碼塊:

synchronized (vector){
    for (int i = 0; i < vector.size(); i++) {
        System.out.println("獲取vector的第" + i + "個元素: " + vector.get(i));
    }
}
synchronized (vector){
    for (int i=0;i<vector.size();i++){
        System.out.println("刪除vector中的第" + i+"個元素");
        vector.remove(i);
    }
}

③ 相對線程安全

  • 相對線程安全需要保證對該對象的單個操作是線程安全的,在必要的時候可以使用同步措施實現線程安全。
  • 大部分的線程安全類都屬於相對線程安全,如Java容器中的Vector、HashTable、通過Collections.synchronizedXXX()方法包裝的集合。

④ 線程相容

  • Java中大部分的類都是線程相容的,通過添加同步措施,可以保證在多線程環境中安全使用這些類的對象。
  • 如常見的ArrayList、HashTableMap都是線程相容的。

⑤ 線程對立

  • 線程對立是指:無法通過添加同步措施,實現多線程中的安全使用。
  • 線程對立的常見操作有:Thread類的suspend()和resume()(已經被JDK聲明廢除),System.setIn()System.setOut()等。

2. Java的枚舉類型

通過enum關鍵字修飾的數據類型,叫枚舉類型。

  • 枚舉類型的每個元素都有自己的序號,通常從0開始編號。
  • 可以通過values()方法遍歷枚舉類型,通過name()或者toString()獲取枚舉類型的名稱
  • 通過ordinal()方法獲取枚舉類型中元素的序號
public class EnumData {
    public static void main(String[] args) {
        for (Family family : Family.values()) {
            System.out.println(family.name() + ":" + family.ordinal());
        }
    }
}

enum Family {
    GRADMOTHER, GRANDFATHER, MOTHER, FATHER, DAUGHTER, SON;
}

可以將枚舉類型看做普通的class,在裡面定義final類型的成員變數,便可以為枚舉類型中的元素賦初值。

要想獲取枚舉類型中元素實際值,需要為成員變數添加getter方法。

雖然枚舉類型的元素有了自己的實際值,但是通過ordinal()方法獲取的元素序號不會發生改變。

public class EnumData {
    public static void main(String[] args) {
        for (Family family : Family.values()) {
            System.out.println(family.name() + ":實際值" + family.getValue() +
                    ", 實際序號" + family.ordinal());
        }
    }
}
enum Family {
    GRADMOTHER(3), GRANDFATHER(4), MOTHER(1), FATHER(2), DAUGHTER(5), SON(6);
    private final int value;
    Family(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

3. Java線程安全的實現

① 互斥同步

互斥同步(Mutex Exclusion & Synchronization)是一種常見的併發正確性保障手段。

  • 同步:多個線程併發訪問共用數據,保證共用數據同一時刻只被一個(或者一些,使用信號量)線程使用。
  • 互斥:互斥是實現同步的一種手段,主要的互斥實現方式:臨界區(Critical Section)、互斥量(Mutex)、信號量(Semaphore)。

同步與互斥的關係:

  • 互斥是原因,同步是結果。
  • 同步是目的,互斥是方法。

Java中,最基本的實現互斥同步的手段是synchronized關鍵字,其次是JUC包中的ReentrantLock。

關於synchronized關鍵字:

  • 編譯後的同步塊,開始處會添加monitorenter指令,結束處或異常處會添加monitorexit指令。
  • monitorenter和monitorexit指令中都包含一個引用類型的參數,分別指向加鎖或解鎖的對象。如果是同步代碼塊,則為synchronized括弧中明確指定的對象;如果為普通方法,則為當前實例對象;如果為靜態方法,則為類對應的class對象。
  • JVM執行monitorenter指令時,要先嘗試獲取鎖:如果對象沒被鎖定或者當前線程已經擁有該對象的鎖,則鎖計數器加1;否則獲取鎖失敗,進入阻塞狀態,等待持有鎖的線程釋放鎖。
  • JVM執行monitorexit指令時,鎖計數器減1,直到計數器的值為0,鎖被釋放。(synchronized是支持重進入的)
  • 由於阻塞或者喚醒線程都需要從用戶態(User Mode)切換到核心態(Kernel Mode),有時鎖只會被持有很短的時間,沒有必要進行狀態轉換。可以讓線程在阻塞之前先自旋等待一段時間,超時未獲取到鎖才進入阻塞狀態,這樣可以避免頻繁的切入到核心態。其實,就是後面自旋鎖的思想。

關於ReentrantLock:

  • 與synchronized關鍵字相比,它是API層面的互斥鎖(lock()、unlock()、try...finally)。
  • 與synchronized關鍵字相比,具有可中斷、支持公平與非公平性、可綁定多個Condition對象的高級功能。
  • 由於synchronized關鍵字被優化,二者的性能差異並不是很大,如果不是想使用ReentrantLock的高級功能,優先考慮使用synchronized關鍵字。

② 非阻塞同步

(1)CAS概述

互斥同步最大的性能問題是線程的阻塞和喚醒,因此又叫阻塞同步。

互斥同步採用悲觀併發策略:

  • 多線程併發訪問共用數據時,總是認為只要不加正確的同步措施,肯定會出現問題。
  • 無論共用數據是否存在競爭,都會執行加鎖、用戶態和心態的切換、維護鎖計數器、檢查是否有被阻塞的線程需要喚醒等操作。

隨著硬體指令集的發展,我們可以採用基於衝突檢測的樂觀併發策略:

  • 先進行操作,如果不存在衝突(即沒有其他線程爭用共用數據),則操作成功。
  • 如果有其他線程爭用共用數據,產生了衝突,使用其他的補償措施。
  • 常見的補償措施:不斷嘗試,直到成功為止,比如迴圈的CAS操作。

樂觀併發策略的許多實現都不需要將線程阻塞,這種同步操作叫做非阻塞同步。

非阻塞同步依靠的硬體指令集:前三條是比較久遠的指令,後兩條是現代處理器新增的。

  • 測試和設置(Test and Set)
  • 獲取並增加(Fetch and Increment)
  • 交換(Swap)
  • 比較並交換(Compare and Swap,即CAS)
  • 載入鏈接/條件存儲(Load Linked/ Store Conditional,即LL/SC)

什麼是CAS?

  • CAS,即Compare and Swap,需要藉助處理器的cmpxchg指令完成。
  • CAS指令需要三個操作數:記憶體位置V(Java中可以簡單的理解為變數的記憶體地址)、舊的期待值A、新值B。
  • CAS指令執行時,當且僅當V符合舊的預期值A,處理器才用新值B更新V的值;否則,不執行更新。
  • 不管是否更新V的值,都返回V的舊值,整個處理過程是一個原子操作。

原子操作:所謂的原子操作是指一個或一系列不可被中斷的操作。

Java中的CAS操作:

  • Java中的CAS操作由sun.misc.Unsafe中的compareAndSwapInt()、compareAndSwapLong()等幾個方法包裝提供。實際無法調用這些方法,需要採用反射機制才能使用。
  • 在實際的開發過程中,一般通過其他的Java API調用它們,如JUC包原子類中的compareAndSet(expect, update) 、getAndIncrement()等方法。這些方法內部都使用了Unsafe類的CAS操作。
  • Unsafe類的CAS操作,通過JVM的即時編譯器編譯後,是一條與平臺相關的CAS指令。

除了偏向鎖,Java中其他鎖的實現方式都是用了迴圈的CAS操作。

(2)通過迴圈的CAS實現原子操作

通過++i或者i++可以實現計數器的自增,在多線程環境下,這樣使用是非線程安全的。

public class UnsafeCount {
    private int i = 0;
    private static final int THREADS_COUNT = 200;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        UnsafeCount counter = new UnsafeCount();
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        counter.count();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("多線程調用計數器i,運行後的值為: " + counter.i);
    }

    public void count() {
        i++;
    }
}

運行以上的代碼發現:當線程數量增加,每個線程調用計數器的次數變大時,每次運行的結果是錯誤且不固定的。

為了實現實在一個多線程環境下、線程安全的計數器,需要使用AtomicInteger的原子自增運算。

import java.util.concurrent.atomic.AtomicInteger;
public class SafeCount {
    private AtomicInteger atomic = new AtomicInteger(0);
    private static final int THREAD_COUNT = 200;
    public static void main(String[] args) {
        SafeCount counter = new SafeCount();
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j=0;j<10000;j++){
                        counter.count();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount()>1){
            Thread.yield();
        }
        System.out.println("多線程調用線程安全的計數器atomic:"+counter.atomic);
    }
    public void count() {
        // 調用compareAnSet方法,使用迴圈的CAS操作實現計數器的原子自增
        for (; ; ) {
            int expect = atomic.get();
            int curVal = expect + 1;
            if (atomic.compareAndSet(expect, curVal)) {
                break;
            }
        }
    }
}

與非線程安全的計數器相比,線程安全的計數器有以下特點:

  • 將int類型的計數器變數i,更換成具有CAS操作的AtomicInteger類型的計數器變數atomic。
  • 進行自增運算時,通過迴圈的CAS操作實現atomic的原子自增。
  • 先通過atomic.get()獲取expect的值,將expect加一得到新值,然後通過atomic.compareAndSet(expect, curVal)這一方法實現CAS操作。
  • 其中compareAndSet()返回的true或者false,表示此次CAS操作是否成功。如果返回false,則不停地重覆執行CAS操作,直到操作成功。

上面的count方法實現的AtomicInteger原子自增,可以只需要調用incrementAndGet()一個方法就能實現。

public void count() {
    // 調用incrementAndGet方法,實現AtomicInteger的原子自增
    atomic.incrementAndGet();
}

因為incrementAndGet()方法,封裝了通過迴圈的CAS操作實現AtomicInteger原子自增的代碼。

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}
(3)CAS操作存在的問題

1. ABA問題

  • 在執行CAS操作更新共用變數的值時,如果一個值原來是A,被其他線程改成了B,然後又改回成了A。對於該CAS操作來說,它完全感受不到共用變數值的變化。這種操作漏洞稱為CAS操作的ABA問題。
  • 解決該問題的思路是,為變數添加版本號,每次更新時版本號遞增。這種場景下就成了1A --> 2B --> 3A。CAS操作就能檢測到共用變數的ABA問題了。
  • JUC包中,也提供了相應的帶標記的原子引用類AtomicStampedReference來解決ABA問題。
  • AtomicStampedReference的compareAndSet()方法會首先比較期待的引用是否等於當前引用,然後檢查期待的標記是否等於當前標記。如果全部相等,則以原子操作的方式將新的引用和新的標記更新到當前值中。
  • 但是AtomicStampedReference目前比較雞肋,如果想解決AB問題,可以使用鎖。

2. 迴圈時間過長,開銷大

迴圈的CAS操作如果長時間不成功,會給CPU帶來非常大的執行開銷。

3. 只能保證一個共用變數的原子操作

  • 只對一個共用變數執行操作時,可以通過迴圈的CAS操作實現。如果是多個共用變數,迴圈的CAS操作無法保證操作的原子性。
  • 取巧的操作:將多個共用變數合為一個變數進行CAS操作。JDK1.5開始,提供了AtomicReference類保證引用對象之間的原子性,可以將多個變數放在一個對象中進行CAS操作。

③ 無同步方案

同步只是保證共用數據爭用時正確性的一種手段,如果不存在共用數據,自然無須任何同步措施。

(1)棧封閉

多個線程訪問同一個方法的局部變數時,不會出現線程安全問題。

因為方法中的局部變數不會逃出該方法而被其他線程訪問,因此可以看做JVM棧中數據,屬於線程私有。

(2)可重入代碼(Reentrant Code)

可重入代碼又叫純代碼(Pure Code),可在代碼執行的任何時候中斷他它,轉去執行另外一段代碼(包括遞歸調用它本身),控制權返回後,原來的程式不會出現任何錯誤。

所有可重入的代碼都是線程安全,並非所有線程安全的代碼都是可重入的。

可重入代碼的共同特征:

  • 不依賴存儲在堆上的數據和公用的系統資源
  • 用到的狀態量都由參數中傳入
  • 不調用非可重用的方法

如何判斷代碼是否具備可重入性?如果一個方法,它的返回結果是可預測的。只要輸入了相同的數據,就都能返回相同的結果,那它就滿足可重入性,當然也就是線程安全的。

(3)線程本地存儲(TLS)

線程本地存儲(Thread Local Storage):

  • 如果一段代碼中所需要的數據必須與其他代碼共用,那就看看這些共用數據的代碼是否能保證在同一個線程中執行。
  • 如果能保證,我們就可以把共用數據的可見範圍限制在同一個線程內。
  • 這樣,無須同步也能保證線程之間不出現數據爭用的問題。

TLS的重要應用實例:經典的Web交互模型中,一個請求對應一個伺服器線程,使得Web伺服器應用可以使用。

Java中沒有關鍵字可以將一個變數定義為線程所獨享,但是Java中創建了java.lang.ThreadLocal類提供線程本地存儲功能。

  • 每一個線程內部都包含一個ThreadLocalMap對象,該對象將ThreadLocal對象的hashCode值作為key,即ThreadLocal.threadLocalHashCode,將本地線程變數作為value,構成鍵值對。
  • ThreadLocal對象是當前線程ThreadLocalMap對象的訪問入口,通過threadLocal.set()為本地線程添加獨享變數;通過threadLocal.get()獲取本地線程獨享變數的值。
  • ThreadLocal、ThreadLocalMap、Thread的關係:Thread對象中包含ThreadLocalMap對象,ThreadLocalMap對象中包含多個鍵值對,每個鍵值對的key是ThreadLocal對象的hashCode,value是本地線程變數。

ThreadLocal的編程實例:

  • 想為某個線程添加本地線程變數,必須通過ThreadLocal對象在該線程中進行添加,構造出的鍵值對自動存入該線程的map中;
  • 想要獲取某個線程的本地線程變數,必須在該線程中獲取,會自動查詢該線程的map,獲得ThreadLocal對象對應的value。
  • 通過ThreadLocal對象重覆為某個線程添加鍵值對,會覆蓋之前的value。
public class TLS {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 設置當前線程的本地線程變數
                threadLocal1.set("thread1");
                threadLocal2.set(1);
                System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
                // 使用完畢後要刪除,避免記憶體泄露
                threadLocal1.remove();
                threadLocal2.remove();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal1.set("thread2");
                threadLocal2.set(2);
                System.out.println(threadLocal1.get() + ": " + threadLocal2.get());
                threadLocal1.remove();
                threadLocal2.remove();
            }
        });
        thread1.start();
        thread2.start();
        // 沒有通過ThreadLocal為主線程添加過本地線程變數,獲取到的內容都是null
        System.out.println(threadLocal1.get()+": "+threadLocal2.get());
    }
}

對ThreadLocal的正確理解:

  • ThreadLocal適用於線程需要有自己的實例變數,該實例變數可以在多個方法中被使用,但是不能被其他線程共用的場景。
  • 由於不存在數據共用,何談同步?因此ThreadLocal 從理論上講,不是用來解決多線程併發問題的。

ThreadLocal的實現:

最原始的想法:ThreadLocal維護線程與實例的映射。既然通過ThreadLocal對象為線程添加本地線程變數,那就將ThreadLocalMap放在ThreadLocal中。

原始想法存在的缺陷:多線程併發訪問ThreadLocal中的Map,需要添加鎖。這是, JDK 未採用該方案的一個原因。

優化後的方法:Thread維護ThreadLocal與實例的映射。Map是每個線程所私有,只能在當前線程通過ThreadLocal對象訪問自身的Map。不存在多線程併發訪問同一個Map的情況,也就不需要鎖。

優化後存在記憶體泄露的情況:JDK1.8中,ThreadLocalMap每個Entry對ThreadLocal對象是弱引用,對每個實例是強引用。當ThreadLocal對象被回收後,該Entry的鍵變成null,但Entry無法被移除。使得實例被Entry引用無法回收,造成記憶體泄露。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


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

-Advertisement-
Play Games
更多相關文章
  • 我們結合運算符重載知識實現string 類 在自己實現的String類中可以參考C++中string的方法 例如構造,加法,大小比較,長度,[] 等操作. 當前的MyString 類中,暫時不加入迭代器,我們將在下一節中加入迭代器的代碼. #include <iostream> using name ...
  • Java異常 1.概念理解 異常(Exepotion)指程式運行過程中不期而至的各種狀況,它阻止了程式按照程式員的預期正常執行,這就是異常(開發過程中的語法錯誤和1.邏輯錯誤不是異常)。如文件找不到,網路連接失敗,非法參數等。 異常發生在程式運行期間,影響程式的正常執行。 2.常見異常分類 **檢查 ...
  • WEB開發會話技術02 6.Cookie的生命周期 預設情況下,Cookie只在瀏覽器的記憶體中存活,也就是說,當你關閉瀏覽器後,Cookie就會消失。但是也可以通過方法設置cookie的生存時間。 cookie的生命周期指的是如何管理cookie,什麼時候cookie被銷毀。 setMaxAge(i ...
  • 全局有序 在RocketMQ中,如果使消息全局有序,可以為Topic設置一個消息隊列,使用一個生產者單線程發送數據,消費者端也使用單線程進行消費,從而保證消息的全局有序,但是這種方式效率低,一般不使用。 局部有序 假設一個Topic分配了兩個消息隊列,生產者在發送消息的時候,可以對消息設置一個路由I ...
  • 目錄 一.簡介 二.freeglut + glew 三.glfw + glad 四.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL E ...
  • 一.小結 1.標識符是程式中事務的名稱 2.標誌符是由字母 數字 下劃線 和美元符號$構成的字元序列 3.標識符必須以字母或下劃線開頭,不能以數字開頭 4.標識符不能是保留字 5.標識符可以是任意長度 6.選擇描述性的標識符可提高程式的可讀性 7.使用變數存儲在程式中使用的數據 8.聲明變數就是告訴 ...
  • 大家好,歡迎來到 Crossin的編程教室 ! 前幾天,後臺老有小伙伴留言“愛心代碼”。這不是Crossin很早之前發過的內容嘛,怎麼最近突然又被人翻出來了?後來才知道,原來是一部有關程式員的青春偶像劇《點燃我,溫暖你》在熱播,而劇中有一段關於期中考試要用程式畫一個愛心的橋段。 於是出於好奇,Cro ...
  • 列表和字典的區別是列表可以通過索引來訪問值,而字典可以通過名稱來訪問各個值。 字典這種數據結構稱為映射(mapping),字典是Python中唯一內置映射類型,值不按照順序排列,而是存儲再鍵下麵。 其中鍵可以是數字、字元串或元組等不可變數據類型。 字典的用途 字典的名稱指出了這種數據結構的用途。日常 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...