主頁 > 後端開發 > 分析原始碼,學會正確使用 Java 執行緒池

分析原始碼,學會正確使用 Java 執行緒池

2020-12-29 06:55:08 後端開發

在日常的開發作業當中,執行緒池往往承載著一個應用中最重要的業務邏輯,因此我們有必要更多地去關注執行緒池的執行情況,包括例外的處理和分析等,本文主要聚焦在如何正確使用執行緒池上,以及提供一些實用的建議,文中會稍微涉及到一些執行緒池實作原理方面的知識,但是不會過多展開,

執行緒池的例外處理

UncaughtExceptionHandler

我們都知道Runnable介面中的run方法是不允許拋出例外的,因此派生出這個執行緒的主執行緒可能無法直接獲得該執行緒在執行程序中的例外資訊,如下例:

	public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
            System.out.println(1 / 0); // 這行會導致報錯!
        });
        thread.setUncaughtExceptionHandler((t, e) -> {
            e.printStackTrace(); //如果你把這一行注釋掉,這個程式將不會拋出任何例外.
        });
        thread.start();
    }

為什么會這樣呢?其實我們看一下Thread中的原始碼就會發現,Thread在執行程序中如果遇到了例外,會先判斷當前執行緒是否有設定UncaughtExceptionHandler,如果沒有,則會從執行緒所在的ThreadGroup中獲取,

注意:每個執行緒都有自己的ThreadGroup,即使你沒有指定,并且它實作了UncaughtExceptionHandler介面,

我們看下ThreadGroup中默認的對UncaughtExceptionHandler介面的實作:

	public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

這個ThreadGroup如果有父ThreadGroup,則呼叫父ThreadGroup的uncaughtException,否則呼叫全域默認的Thread.DefaultUncaughtExceptionHandler,如果全域的handler也沒有設定,則只是簡單地將例外資訊定位到System.err中,這就是為什么我們應當在創建執行緒的時候,去實作它的UncaughtExceptionHandler介面的原因,這么做可以讓你更方便地去排查問題,

通過execute提交任務給執行緒池

回到執行緒池這個話題,如果我們向執行緒池提交的任務中,沒有對例外進行try...catch處理,并且運行的時候出現了例外,那會對執行緒池造成什么影響呢?答案是沒有影響,執行緒池依舊可以正常作業,但是例外卻被吞掉了,這通常來說不是一個好事情,因為我們需要拿到原始的例外物件去分析問題,

那么怎樣才能拿到原始的例外物件呢?我們從執行緒池的原始碼著手開始研究這個問題,當然網上關于執行緒池的原始碼決議文章有很多,這里限于篇幅,直接給出最相關的部分代碼:

	final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

這個方法就是真正去執行提交給執行緒池的任務的代碼,

這里我們略去其中不相關的邏輯,重點關注第19行到第32行的邏輯,其中第23行是真正開始執行提交給執行緒池的任務,那么第20行是干什么的呢?其實就是在執行提交給執行緒池的任務之前可以做一些前置作業,同樣的,我們看到第31行,這個是在執行完提交的任務之后,可以做一些后置作業,

beforeExecute這個我們暫且不管,重點關注下afterExecute這個方法,我們可以看到,在執行任務程序中,一旦拋出任何型別的例外,都會提交給afterExecute這個方法,然而查看執行緒池的源代碼我們可以發現,默認的afterExecute是個空實作,因此,我們有必要繼承ThreadPoolExecutor去實作這個afterExecute方法,

看原始碼我們可以發現這個afterExecute方法是protected型別的,從官方注釋上也可以看到,這個方法就是推薦子類去實作的,

當然,這個方法不能隨意去實作,需要遵循一定的步驟,具體的官方注釋也有講,這里摘抄如下:

	 *  <pre> {@code
	 * class ExtendedExecutor extends ThreadPoolExecutor {
	 *   // ...
	 *   protected void afterExecute(Runnable r, Throwable t) {
	 *     super.afterExecute(r, t);
	 *     if (t == null && r instanceof Future<?>) {
	 *       try {
	 *         Object result = ((Future<?>) r).get();
	 *       } catch (CancellationException ce) {
	 *           t = ce;
	 *       } catch (ExecutionException ee) {
	 *           t = ee.getCause();
	 *       } catch (InterruptedException ie) {
	 *           Thread.currentThread().interrupt(); // ignore/reset
	 *       }
	 *     }
	 *     if (t != null)
	 *       System.out.println(t);
	 *   }
	 * }}</pre>

那么通過這種方式,就可以將原先可能被執行緒池吞掉的例外成功捕獲到,從而便于排查問題,

但是這里還有個小問題,我們注意到在runWorker方法中,執行task.run();陳述句之后,各種型別的例外都被拋出了,那這些被拋出的例外去了哪里?事實上這里的例外物件最侄訓被傳入到Thread的dispatchUncaughtException方法中,原始碼如下:

	private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

可以看到它會去獲取UncaughtExceptionHandler的實作類,然后呼叫其中的uncaughtException方法,這也就回到了我們上一小節所分析的UncaughtExceptionHandler實作的具體邏輯,那么為了拿到最原始的例外物件,除了實作UncaughtExceptionHandler介面之外,也可以考慮實作afterExecute方法,

通過submit提交任務到執行緒池

這個同樣很簡單,我們還是先回到submit方法的原始碼:

	public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

這里的execute方法呼叫的是ThreadPoolExecutor中的execute方法,執行邏輯跟通過execute提交任務到執行緒池是一樣的,我們先重點關注這里的newTaskFor方法,其原始碼如下:

	protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

可以看到提交的Callable物件用FutureTask封裝起來了,我們知道最侄訓執行到上述runWorker這個方法中,并且最核心的執行邏輯就是task.run();這行代碼,我們知道這里的task其實是FutureTask型別,因此我們有必要看一下FutureTask中的run方法的實作:

	public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

可以看到這其中跟例外相關的最關鍵的代碼就在第17行,也就是setException(ex);這個地方,我們看一下這個地方的實作:

	protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

這里最關鍵的地方就是將例外物件賦值給了outcome,outcome是FutureTask中的成員變數,我們通過呼叫submit方法,拿到一個Future物件之后,再呼叫它的get方法,其中最核心的方法就是report方法,下面給出每個方法的原始碼:

首先是get方法:

	public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

可以看到最終呼叫了report方法,其原始碼如下:

	private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

上面是一些狀態判斷,如果當前任務不是正常執行完畢,或者被取消的話,那么這里的x其實就是原始的例外物件,可以看到會被ExecutionException包裝,因此在你呼叫get方法時,可能會拋出ExecutionException例外,那么呼叫它的getCause方法就可以拿到最原始的例外物件了,

綜上所述,針對提交給執行緒池的任務可能會拋出例外這一問題,主要有以下兩種處理思路:

  1. 在提交的任務當中自行try...catch,但這里有個不好的地方就是如果你會提交多種型別的任務到執行緒池中,每種型別的任務都需要自行將例外try...catch住,比較繁瑣,而且如果你只是catch(Exception e),可能依然會漏掉一些包括Error型別的例外,那為了保險起見,可以考慮catch(Throwable t),
  2. 自行實作執行緒池的afterExecute方法,或者實作Thread的UncaughtExceptionHandler介面,

下面給出我個人創建執行緒池的一個示例,供大家參考:

	BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(DEFAULT_QUEUE_SIZE);
    statisticsThreadPool = new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAX_POOL_SIZE,
            60, TimeUnit.SECONDS, queue, new ThreadFactoryBuilder()
            .setThreadFactory(new ThreadFactory() {
                private int count = 0;
                private String prefix = "StatisticsTask";

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, prefix + "-" + count++);
                }
            }).setUncaughtExceptionHandler((t, e) -> {
                String threadName = t.getName();
                logger.error("statisticsThreadPool error occurred! threadName: {}, error msg: {}", threadName, e.getMessage(), e);
            }).build(), (r, executor) -> {
        if (!executor.isShutdown()) {
            logger.warn("statisticsThreadPool is too busy! waiting to insert task to queue! ");
            Uninterruptibles.putUninterruptibly(executor.getQueue(), r);
        }
    }) {
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
            if (t == null && r instanceof Future<?>) {
                try {
                    Future<?> future = (Future<?>) r;
                    future.get();
                } catch (CancellationException ce) {
                    t = ce;
                } catch (ExecutionException ee) {
                    t = ee.getCause();
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                logger.error("statisticsThreadPool error msg: {}", t.getMessage(), t);
            }
        }
    };
    statisticsThreadPool.prestartAllCoreThreads();

執行緒數的設定

我們知道任務一般有兩種:CPU密集型和IO密集型,那么面對CPU密集型的任務,執行緒數不宜過多,一般選擇CPU核心數+1或者核心數的2倍是比較合理的一個值,因此我們可以考慮將corePoolSize設定為CPU核心數+1,maxPoolSize設定為核心數的2倍,

同樣的,面對IO密集型任務時,我們可以考慮以核心數乘以4倍作為核心執行緒數,然后核心數乘以5倍作為最大執行緒數的方式去設定執行緒數,這樣的設定會比直接拍腦袋設定一個值會更合理一些,

當然總的執行緒數不宜過多,控制在100個執行緒以內比較合理,否則執行緒數過多可能會導致頻繁地背景關系切換,導致系統性能反不如前,

如何正確關閉一個執行緒池

說到如何正確去關閉一個執行緒池,這里面也有點講究,為了實作優雅停機的目標,我們應當先呼叫shutdown方法,呼叫這個方法也就意味著,這個執行緒池不會再接收任何新的任務,但是已經提交的任務還會繼續執行,包括佇列中的,所以,之后你還應當呼叫awaitTermination方法,這個方法可以設定執行緒池在關閉之前的最大超時時間,如果在超時時間結束之前執行緒池能夠正常關閉,這個方法會回傳true,否則,一旦超時,就會回傳false,通常來說我們不可能無限制地等待下去,因此需要我們事先預估一個合理的超時時間,然后去使用這個方法,

如果awaitTermination方法回傳false,你又希望盡可能在執行緒池關閉之后再做其他資源回收作業,可以考慮再呼叫一下shutdownNow方法,此時佇列中所有尚未被處理的任務都會被丟棄,同時會設定執行緒池中每個執行緒的中斷標志位,shutdownNow并不保證一定可以讓正在運行的執行緒停止作業,除非提交給執行緒的任務能夠正確回應中斷,到了這一步,可以考慮繼續呼叫awaitTermination方法,或者直接放棄,去做接下來要做的事情,

執行緒池中的其他有用方法

大家可能有留意到,我在創建執行緒池的時候,還呼叫了這個方法:prestartAllCoreThreads,這個方法有什么作用呢?我們知道一個執行緒池創建出來之后,在沒有給它提交任何任務之前,這個執行緒池中的執行緒數為0,有時候我們事先知道會有很多任務會提交給這個執行緒池,但是等它一個個去創建新執行緒開銷太大,影響系統性能,因此可以考慮在創建執行緒池的時候就將所有的核心執行緒全部一次性創建完畢,這樣系統起來之后就可以直接使用了,

其實執行緒池中還提供了其他一些比較有意思的方法,比如我們現在設想一個場景,當一個執行緒池負載很高,快要撐爆導致觸發拒絕策略時,有沒有什么辦法可以緩解這一問題?其實是有的,因為執行緒池提供了設定核心執行緒數和最大執行緒數的方法,它們分別是setCorePoolSize方法setMaximumPoolSize方法,是的,執行緒池創建完畢之后也是可以更改其執行緒數的!因此,面對執行緒池高負荷運行的情況,我們可以這么處理:

  1. 起一個定時輪詢執行緒(守護型別),定時檢測執行緒池中的執行緒數,具體來說就是呼叫getActiveCount方法,
  2. 當發現執行緒數超過了核心執行緒數大小時,可以考慮將CorePoolSize和MaximumPoolSize的數值同時乘以2,當然這里不建議設定很大的執行緒數,因為并不是執行緒越多越好的,可以考慮設定一個上限值,比如50、100之類的,
  3. 同時,去獲取佇列中的任務數,具體來說是呼叫getQueue方法再呼叫size方法,當佇列中的任務數少于佇列大小的二分之一時,我們可以認為現在執行緒池的負載沒有那么高了,因此可以考慮在執行緒池先前有擴容過的情況下,將CorePoolSize和MaximumPoolSize還原回去,也就是除以2,

具體來說如下圖:

file
以上是我個人建議的一種使用執行緒池的方式,

執行緒池一定是最佳方案嗎?

執行緒池并非在任何情況下都是性能最優的方案,如果是一個追求極致性能的場景,可以考慮使用Disruptor,這是一個高性能佇列,排除Disruptor不談,單純基于JDK的話會不會有更好的方案?答案是有的,

我們知道在一個執行緒池中,多個執行緒是共用一個佇列的,因此在任務很多的情況下,需要對這個佇列進行頻繁讀寫,為了防止沖突因此需要加鎖,事實上在閱讀執行緒池源代碼的時候就可以發現,里面充斥著各種加鎖的代碼,那有沒有更好的實作方式呢?

其實我們可以考慮創建一個由單執行緒執行緒池構成的串列,每個執行緒池都使用有界佇列這種方式去實作多執行緒,這么做的好處是,每個執行緒池中的佇列都只會被一個執行緒去操作,這樣就沒有競爭的問題,

其實這種用空間換時間的思路借鑒了Netty中EventLoop的實作機制,試想,如果執行緒池的性能真的有那么好,為什么Netty不用呢?

其他需要注意的地方

  1. 任何情況下都不應該使用可伸縮執行緒池(執行緒的創建和銷毀開銷是很大的),
  2. 任何情況下都不應該使用無界佇列,單測除外,有界佇列常用的有ArrayBlockingQueue和LinkedBlockingQueue,前者基于陣列實作,后者基于鏈表,從性能表現上來看,LinkedBlockingQueue的吞吐量更高但是性能并不穩定,實際情況下應當使用哪一種建議自行測驗之后決定,順便說一句,Executors的newFixedThreadPool采用的是LinkedBlockingQueue,
  3. 推薦自行實作RejectedExecutionHandler,JDK自帶的都不是很好用,你可以在里面實作自己的邏輯,如果需要一些特定的背景關系資訊,可以在Runnable實作類中添加一些自己的東西,這樣在RejectedExecutionHandler中就可以直接使用了,

怎樣做到不丟任務

這里其實指的是一種特殊情況,就是比如突然遇到了一股流量尖峰,導致執行緒池負載已經非常高了,即快要觸發拒絕策略的時候,我們可以怎么做來盡量防止提交的任務丟失,一般來說當遇到這種情況的時候,應當盡快觸發報警通知研發人員來處理,之后不管是限流也好,還是增加機器也好,甚至是上Kafka、Redis甚至是資料庫用來暫存任務資料也是可以的,但畢竟遠水救不了近火,如果我們希望在正式解決這個問題之前,先盡可能地緩解,可以考慮怎么做呢?

首先可以考慮的就是我前面提到的動態增大執行緒池中的執行緒數,但是假如已經擴容過了,此時不應繼續擴容,否則可能導致系統的吞吐量更低,在這種情況下,應當自行實作RejectedExecutionHandler,具體來說就是在實作類中,單獨開一個單執行緒的執行緒池,然后呼叫原執行緒池的getQueue方法的put方法,將塞不進去的任務再次嘗試塞進去,當然在佇列滿的時候是塞不進去的,但那至少也只是阻塞了這個單獨的執行緒而已,并不影響主流程,

當然,這種方案是治標不治本的,面對流量激增這種場景其實業界有很多成熟的做法,只是單純從執行緒池的角度來看的話,這種方式不失為一種臨時有效的解決方案,

來源:https://my.oschina.net/editorial-story/blog/3107684
歡迎關注公眾號 【碼農開花】一起學習成長
我會一直分享Java干貨,也會分享免費的學習資料課程和面試寶典
回復:【計算機】【設計模式】【面試】有驚喜哦

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

標籤:Java

上一篇:模板引擎

下一篇:強大:MyBatis ,三種流式查詢方法

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