主頁 > 後端開發 > 萬字詳解 Java 執行緒安全,面試必備!

萬字詳解 Java 執行緒安全,面試必備!

2022-11-22 07:08:20 後端開發

來源: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 = https://www.cnblogs.com/javastack/archive/2022/11/21/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開發手冊(嵩山版)》最新發布,速速下載!

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

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

標籤:其他

上一篇:mybatis-plus入門

下一篇:10python字典

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

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more