摘要:手動(dòng)創(chuàng)建執(zhí)行線程存在以上問題,而線程池就是用來(lái)解決這些問題的。線程池詳解上面我們已經(jīng)知道了線程池的作用,而對(duì)于這樣一個(gè)好用,重要的工具,當(dāng)然已經(jīng)為我們提供了實(shí)現(xiàn),這也是本篇文章的重點(diǎn)。,線程池一旦空閑超過時(shí)間,線程都將被回收。
本文原創(chuàng)地址,我的博客:https://jsbintask.cn/2019/03/10/jdk/jdk8-threadpool/(食用效果最佳),轉(zhuǎn)載請(qǐng)注明出處!前言
在實(shí)際工作中,線程是一個(gè)我們經(jīng)常要打交道的角色,它可以幫我們靈活利用資源,提升程序運(yùn)行效率。但是我們今天不是探討線程!我們今天來(lái)聊聊另一個(gè)與線程息息相關(guān)的角色:線程池.本篇文章的目的就是全方位的解析線程池的作用,以及jdk中的接口,實(shí)現(xiàn)以及原理,另外對(duì)于某些重要概念,將從源碼的角度探討。
tip:本文較長(zhǎng),建議先碼后看。
首先我們看一段創(chuàng)建線程并且運(yùn)行的常用代碼:
for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println("run thread->" + Thread.currentThread().getName()); //to do something, send email, message, io operator, network... }).start(); }
上面的代碼很容易理解,我們?yōu)榱水惒剑蛘咝士紤],將某些耗時(shí)操作放入一個(gè)新線程去運(yùn)行,但是這樣的代碼卻存在這樣的問題:
創(chuàng)建銷毀線程資源消耗; 我們使用線程的目的本是出于效率考慮,可以為了創(chuàng)建這些線程卻消耗了額外的時(shí)間,資源,對(duì)于線程的銷毀同樣需要系統(tǒng)資源。
cpu資源有限,上述代碼創(chuàng)建線程過多,造成有的任務(wù)不能即時(shí)完成,響應(yīng)時(shí)間過長(zhǎng)。
線程無(wú)法管理,無(wú)節(jié)制地創(chuàng)建線程對(duì)于有限的資源來(lái)說(shuō)似乎成了“得不償失”的一種作用。
手動(dòng)創(chuàng)建執(zhí)行線程存在以上問題,而線程池就是用來(lái)解決這些問題的。怎么解決呢?我們可以先粗略的定義一下線程池:
線程池是一組已經(jīng)創(chuàng)建好的,一直在等待任務(wù)執(zhí)行的線程的集合。
因?yàn)榫€程池中線程是已經(jīng)創(chuàng)建好的,所以對(duì)于任務(wù)的執(zhí)行不會(huì)消耗掉額外的資源,線程池中線程個(gè)數(shù)由我們自定義添加,可相對(duì)于資源,資源任務(wù)做出調(diào)整,對(duì)于某些任務(wù),如果線程池尚未執(zhí)行,可手動(dòng)取消,線程任務(wù)變得能夠管理!
所以,線程池的作用如下:
降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
提高線程的可管理性。
jdk線程池詳解上面我們已經(jīng)知道了線程池的作用,而對(duì)于這樣一個(gè)好用,重要的工具,jdk當(dāng)然已經(jīng)為我們提供了實(shí)現(xiàn),這也是本篇文章的重點(diǎn)。
在jdk中,關(guān)于線程池的接口,類都定義在juc(java.util.concurrent)包中,這是jdk專門為我們提供用于并發(fā)編程的包,當(dāng)然,本篇文章我們只介紹與線程池有關(guān)的接口和類,首先我們看下重點(diǎn)要學(xué)習(xí)的接口和類:
如圖所示,我們將一一講解這6個(gè)類的作用并且分析。
首先我們需要了解就是Executor接口,它有一個(gè)方法,定義如下:
Executor自jdk1.5引入,這個(gè)接口只有一個(gè)方法execute聲明,它的作用以及定義如下:接收一個(gè)任務(wù)(Runnable)并且執(zhí)行。注意:同步執(zhí)行還是異步執(zhí)行均可!
由它的定義我們就知道,它是一個(gè)線程池最基本的作用。但是在實(shí)際使用中,我們常常使用的是另外一個(gè)功能更多的子類ExecutorService。
這個(gè)接口繼承自Executor,它的方法定義就豐富多了,可以關(guān)閉,提交Future任務(wù),批量提交任務(wù),獲取執(zhí)行結(jié)果等,我們一一講解下每個(gè)方法作用聲明:
void shutdown(): “優(yōu)雅地”關(guān)閉線程池,為什么是“優(yōu)雅地”呢?因?yàn)檫@個(gè)線程池在關(guān)閉前會(huì)先等待線程池中已經(jīng)有的任務(wù)執(zhí)行完成,一般會(huì)配合方法awaitTermination一起使用,調(diào)用該方法后,線程池中不能再加入新的任務(wù)。
List
boolean awaitTermination(long timeout, TimeUnit unit): 該方法是一個(gè)阻塞方法,參數(shù)分別為時(shí)間和時(shí)間單位。這個(gè)方法一般配合上面兩個(gè)方法之后調(diào)用。如果先調(diào)用shutdown方法,所有任務(wù)執(zhí)行完成返回true,超時(shí)返回false,如果先調(diào)用的是shutdownNow方法,正在執(zhí)行的任務(wù)全部完成true,超時(shí)返回false。
boolean isTerminated();: 調(diào)用方法1或者2后,如果所有人物全部執(zhí)行完畢則返回true,也就是說(shuō),就算所有任務(wù)執(zhí)行完畢,但是不是先調(diào)用1或者2,也會(huì)返回false。
result = exec.submit(aCallable).get();,提交一個(gè)任務(wù)并且一直阻塞知道該任務(wù)執(zhí)行完成獲取到返回結(jié)果。
Future> submit(Runnable task);:和6不同的是調(diào)用Future.get()方法返回的是null(這又是什么操作?)。
當(dāng)調(diào)用其中任一Future.isDone()(判斷任務(wù)是否完成,正常,異常終止都算)方法時(shí),必須等到所有任務(wù)都完成時(shí)才返回true,簡(jiǎn)單說(shuō):全部任務(wù)完成才算完成。
到現(xiàn)在,我們已經(jīng)知道了一個(gè)線程池基本的所有方法,知道了每個(gè)方法的作用,接下來(lái)我們就來(lái)看看具體實(shí)現(xiàn),首先我們研究下ExecutorService的具體實(shí)現(xiàn)抽象類:AbstractExecutorService。
AbstractExecutorService
AbstractExecutorService是一個(gè)抽象類,繼承自ExecutorService,它實(shí)現(xiàn)了ExecutorService接口的submit, invokeAll, invokeAny方法,主要用于將ExecutorService的公共實(shí)現(xiàn)封裝,方便子類更加方便使用,接下來(lái)我們看看具體實(shí)現(xiàn):
public Future> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFutureftask = newTaskFor(task, null); execute(ftask); return ftask; } protected RunnableFuture newTaskFor(Callable callable) { return new FutureTask (callable); }
判空
利用task構(gòu)建一個(gè)Future的子類RunnableFuture,最后返回
執(zhí)行這個(gè)任務(wù)(execute方法聲明在Executor接口中,所以也是交由子類實(shí)現(xiàn))。
execute方法交由子類實(shí)現(xiàn)了,這里我們主要分析newTaskFor方法,看它是如何構(gòu)建Future對(duì)象的:
首先,RunnableFuture接口定義如下:
public interface RunnableFutureextends Runnable, Future { void run(); }
他就是Future和Runnable的組合,它的實(shí)現(xiàn)是FutureTask:
publicList > invokeAll(Collection extends Callable > tasks) throws InterruptedException { if (tasks == null) throw new NullPointerException(); ArrayList > futures = new ArrayList >(tasks.size()); boolean done = false; // ① try { for (Callable t : tasks) { // ② RunnableFuture f = newTaskFor(t); futures.add(f); execute(f); } for (int i = 0, size = futures.size(); i < size; i++) { Future f = futures.get(i); // ③ if (!f.isDone()) { try { f.get(); } catch (CancellationException ignore) { } catch (ExecutionException ignore) { } } } done = true; // ④ return futures; } finally { if (!done) // ⑤ for (int i = 0, size = futures.size(); i < size; i++) futures.get(i).cancel(true); } }
聲明一個(gè)flag判斷所有任務(wù)是否全部完成
調(diào)用newTaskFor方法構(gòu)建RunnableFuture對(duì)象,循環(huán)調(diào)用execute方法添加每一個(gè)任務(wù)。
遍歷每個(gè)任務(wù)結(jié)果,判斷是否執(zhí)行完成,沒有完成調(diào)用 get()阻塞方法等待完成。
所有任務(wù)全部完成,將flag設(shè)置成true。
出現(xiàn)異常,還有任務(wù)沒有完成,所有任務(wù)取消:Future.cancel()(實(shí)際是調(diào)用執(zhí)行線程的interrupt方法。
上面代碼分析和我們一開始講解ExecutorService的invokeAll一致。
3. invokeAny方法
invokeAny實(shí)際調(diào)用doInvokeAny:
privateT doInvokeAny(Collection extends Callable > tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { if (tasks == null) throw new NullPointerException(); int ntasks = tasks.size(); if (ntasks == 0) throw new IllegalArgumentException(); ArrayList > futures = new ArrayList >(ntasks); ExecutorCompletionService ecs = // ① new ExecutorCompletionService (this); try { ExecutionException ee = null; final long deadline = timed ? System.nanoTime() + nanos : 0L; Iterator extends Callable > it = tasks.iterator(); futures.add(ecs.submit(it.next())); // ② --ntasks; int active = 1; for (;;) { Future f = ecs.poll(); // ③ if (f == null) { if (ntasks > 0) { --ntasks; futures.add(ecs.submit(it.next())); ++active; } else if (active == 0) break; else if (timed) { f = ecs.poll(nanos, TimeUnit.NANOSECONDS); if (f == null) throw new TimeoutException(); nanos = deadline - System.nanoTime(); } else // ④ f = ecs.take(); } if (f != null) { // ⑤ --active; try { return f.get(); } catch (ExecutionException eex) { ee = eex; } catch (RuntimeException rex) { ee = new ExecutionException(rex); } } } if (ee == null) ee = new ExecutionException(); throw ee; } finally { for (int i = 0, size = futures.size(); i < size; i++) // ⑥ futures.get(i).cancel(true); } }
聲明一個(gè)ExecutorCompletionService ecs,這個(gè)對(duì)象實(shí)際是一個(gè)任務(wù)執(zhí)行結(jié)果阻塞隊(duì)列和線程池的結(jié)合,所以它可以加入任務(wù),執(zhí)行任務(wù),將任務(wù)執(zhí)行結(jié)果加入阻塞隊(duì)列。
向ecs添加tasks中的第一個(gè)任務(wù)并且執(zhí)行。
從ecs的阻塞隊(duì)列中取出第一個(gè)(隊(duì)頭),如果為null(不為null跳到注釋⑤),說(shuō)明一個(gè)任務(wù)都還沒執(zhí)行完成,繼續(xù)添加任務(wù)。
如果所有任務(wù)都被添加了,阻塞等待任務(wù)的執(zhí)行結(jié)果,知道有任一任務(wù)執(zhí)行完成。
如果取到了某個(gè)任務(wù)的執(zhí)行結(jié)果,直接返回。
取消所有還沒執(zhí)行的任務(wù)。
上面代碼分析和我們一開始講解ExecutorService的invokeAny一致。 到現(xiàn)在,我們已經(jīng)分析完了AbstractExecutorService中的公共的方法,接下來(lái)就該研究最終的具體實(shí)現(xiàn)了:ThreadPoolExecutor
ThreadPoolExecutorThreadPoolExecutor繼承自AbstractExecutorService,它是線程池的具體實(shí)現(xiàn):
我們首先分析下構(gòu)造方法:`public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)`。
corePoolSize:核心線程數(shù),maximumPoolSize:線程池最大允許線程數(shù),workQueue:任務(wù)隊(duì)列,threadFactory:線程創(chuàng)建工廠,handler: 任務(wù)拒絕策,keepAliveTime, unit:等待時(shí)長(zhǎng),它們的具體作用如下:
提交一個(gè)task(Runnable)后(執(zhí)行execute方法),檢查總線程數(shù)是否小于corePoolSize,小于等于則使用threadFactory直接創(chuàng)建一個(gè)線程執(zhí)行任務(wù),大于則再次檢查線程數(shù)量是否等于maximumPoolSize,等于則直接執(zhí)行handler拒絕策略,小于則判斷workQueue是否已經(jīng)滿了,沒滿則將任務(wù)加入等待線程執(zhí)行,滿了則使用threadFactory創(chuàng)建新線程執(zhí)行隊(duì)頭任務(wù)。
通過流程圖我們知道每個(gè)參數(shù)作用,這里值得注意的是,如果我們將某些參數(shù)特殊化,則可以得到特殊的線程池:
corePoolSize=maximuPoolSize,我們可以創(chuàng)建一個(gè)線程池線程數(shù)量固定的任務(wù)。
maximumPoolSize設(shè)置的足夠大(Integer.MAX_VALUE),可以無(wú)限制的加入任務(wù)。
workQueue設(shè)置的足夠大,線程池中的數(shù)量不會(huì)超過corePoolSize,此時(shí)maximumPoolSize參數(shù)無(wú)用。
corePoolSize=0,線程池一旦空閑(超過時(shí)間),線程都將被回收。
我們上面知道,如果多余的空閑線程空閑時(shí)間超過keepAliveTime*unit,這些線程將被回收。我們可以通過方法allowCoreThreadTimeOut使這個(gè)參數(shù)對(duì)線程池中所有線程都有效果。
workQueue一般有三種實(shí)現(xiàn):
SynchronousQueue,這是一個(gè)空隊(duì)列,不會(huì)保存提交的task(添加操作必須等待另外的移除操作)。
ArrayBlockingQueue,數(shù)組實(shí)現(xiàn)的丟列,可以指定隊(duì)列的長(zhǎng)度。
LinkedBlockingQueue, 鏈表實(shí)現(xiàn)的隊(duì)列,所以理論上可以無(wú)限大,也可以指定鏈表長(zhǎng)度。
而RejectedExecutionHandler一般由四種實(shí)現(xiàn):
AbortPolicy, 直接拋出RejectedExecutionException,這是線程池中的默認(rèn)實(shí)現(xiàn)
DiscardPolicy,什么都不做
DiscardOldestPolicy,丟棄workQueue隊(duì)頭任務(wù),加入新任務(wù)
CallerRunsPolicy,直接在調(diào)用者的線程執(zhí)行任務(wù)
最后,我們?cè)俜治鱿耇hreadPoolExecutor核心方法execute:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // ① if (workerCountOf(c) < corePoolSize) { // ② if (addWorker(command, true)) return; c = ctl.get(); // ③ } if (isRunning(c) && workQueue.offer(command)) { // ④ int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) // ⑤ reject(command); else if (workerCountOf(recheck) == 0) // ⑥ addWorker(null, false); } else if (!addWorker(command, false)) // ⑦ reject(command); }
獲取線程池中的線程數(shù)量
線程池中線程數(shù)量小于corePoolSize,直接調(diào)用addWorker添加新線程執(zhí)行任務(wù)返回。
因?yàn)槎嗑€程的關(guān)系,上一步可能調(diào)用addWorker失敗(其它線程創(chuàng)建了,數(shù)以數(shù)量已經(jīng)超過了),重啟獲取線程數(shù)量。
向workQueue添加添加任務(wù),如果添加成功,double獲取線程數(shù)量,添加失敗,走到步驟⑦
double檢查后發(fā)現(xiàn)線程池已經(jīng)關(guān)閉或者數(shù)量超出,回滾已經(jīng)添加的任務(wù)(remove(command))并且執(zhí)行拒絕策略。
double檢查通過,添加一個(gè)新線程。
再次添加線程,失敗則調(diào)用拒絕策略。
好了,到現(xiàn)在jdk中的線程池核心的實(shí)現(xiàn),策略,分析我們已經(jīng)分析完成了。接下來(lái)我我們就來(lái)看看關(guān)于線程池的另外的一些擴(kuò)展,也就是圖上的剩下的接口和類:
ScheduledExecutorService繼承自ExecutorService,ExecutorService的分析上面我們已經(jīng)知道了,我們來(lái)看看它擴(kuò)展了哪些方法:
這個(gè)接口作為線程池的定義主要增加了可以定時(shí)執(zhí)行任務(wù)(執(zhí)行一次)和定期執(zhí)行任務(wù)(重復(fù)執(zhí)行),我們來(lái)一一簡(jiǎn)述下每個(gè)方法的作用。
public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit);: 這個(gè)方法用于定時(shí)執(zhí)行任務(wù)command,延遲的時(shí)間為delay*unit,它返回一個(gè)ScheduledFuture對(duì)象用于獲取執(zhí)行結(jié)果或者剩余延時(shí),調(diào)用Future.get()方法將阻塞當(dāng)前線程最后返回null。
public
public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit);: 重復(fù)執(zhí)行任務(wù)command,第一次執(zhí)行時(shí)間為initialDelay延遲后,以后的執(zhí)行時(shí)間將在initialDelay + period * n,unit代表時(shí)間單位,值得注意的是,如果某次執(zhí)行出現(xiàn)異常,后面該任務(wù)就不會(huì)再執(zhí)行。或者通過返回對(duì)象Future手動(dòng)取消,后面也將不再執(zhí)行。
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay, TimeUnit unit);: 效果同上,不同點(diǎn):如果command耗時(shí)為 y,則上面的計(jì)算公式為initialDelay + period * n + y,也就是說(shuō),它的定時(shí)時(shí)間會(huì)加上任務(wù)耗時(shí),而上面的方法則是一個(gè)固定的頻率,不會(huì)算上任務(wù)執(zhí)行時(shí)間!
這是它擴(kuò)展的四個(gè)方法,其中需要注意的是scheduleAtFixedRate和scheduleWithFixedDelay的細(xì)微差別,最后,我們來(lái)看下它的實(shí)現(xiàn)類:ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutorScheduledThreadPoolExecutor繼承自ThreadPoolExecutor類,實(shí)現(xiàn)了ScheduledExecutorService接口,上面均已經(jīng)分析。
它的構(gòu)造器如下:
看起來(lái)比它的父類構(gòu)造器簡(jiǎn)潔,主要因?yàn)樗娜蝿?wù)隊(duì)列workQueue是默認(rèn)的(DelayedWorkQueue),并且最大的線程數(shù)為最大值。接著我們看下DelayedWorkQueue實(shí)現(xiàn):
它內(nèi)部使用數(shù)組維護(hù)了一個(gè)二叉樹,提高了任務(wù)查找時(shí)間,而之所以ScheduledThreadPoolExecutor能夠?qū)崿F(xiàn)延時(shí)的關(guān)鍵也在于DelayedWorkQueue的take()方法:
public RunnableScheduledFuture> take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { // ① RunnableScheduledFuture> first = queue[0]; if (first == null) available.await(); else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return finishPoll(first); first = null; // don"t retain ref while waiting if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && queue[0] != null) available.signal(); lock.unlock(); } }
工作線程調(diào)用take方法獲取剩余任務(wù)。
檢查這個(gè)任務(wù)是否已經(jīng)到了執(zhí)行時(shí)間。
未到執(zhí)行時(shí)間,await等待。
自己?jiǎn)拘眩M(jìn)入循環(huán)再次計(jì)算時(shí)間。
好了,到目前為止jdk中關(guān)于線程池的6個(gè)核心類已經(jīng)全部分析完畢了。接下來(lái)還有最后一個(gè)小問題,我們手動(dòng)創(chuàng)建線程池參數(shù)也太了,不管是ThreadPoolExecutor還是ScheduledThreadPoolExecutor,這對(duì)于用戶來(lái)說(shuō)似乎并不太友好,當(dāng)然,jdk已經(jīng)想到了這個(gè)問題,所以,我們最后再介紹一個(gè)創(chuàng)建這些線程池的工具類:Executors:
Executors它的主要工具方法如下:
比起手動(dòng)創(chuàng)建,它幫我們加了很多默認(rèn)值,用起來(lái)當(dāng)然就方便多了,比如說(shuō)newFixedThreadPool
創(chuàng)建一個(gè)線程數(shù)固定的線程池,其實(shí)就是核心線程數(shù)等于最大線程數(shù),和我們一開始分析的結(jié)果一樣。
值得注意的是:為了我們的程序安全可控性考慮,我們應(yīng)該盡量考慮手動(dòng)創(chuàng)建線程池,知曉每一個(gè)參數(shù)的作用,降低不穩(wěn)定性!
本次,我們首先從代碼出發(fā),分析了線程池給我們帶來(lái)的好處以及直接使用線程的弊端,接著引出了jdk中的已經(jīng)實(shí)現(xiàn)了的線程池。然后重點(diǎn)分析了jdk中關(guān)于線程池的六個(gè)最重要的接口和類,并且從源碼角度講解了關(guān)鍵點(diǎn)實(shí)現(xiàn),最后,處于方便考慮,我們還知道jdk給我們留了一個(gè)創(chuàng)建線程池的工具類,簡(jiǎn)化了手動(dòng)創(chuàng)建線程池的步驟。
真正做到了知其然,知其所以然。
關(guān)注我,這里只有干貨!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77459.html
摘要:所以查閱官方文檔以及他人造好的輪子,總結(jié)了一些面試和學(xué)習(xí)中你必須掌握的問題。在微博應(yīng)用中,可以將一個(gè)用戶所有的關(guān)注人存在一個(gè)集合中,將其所有粉絲存在一個(gè)集合。 昨天寫了一篇自己搭建redis集群并在自己項(xiàng)目中使用的文章,今天早上看別人寫的面經(jīng)發(fā)現(xiàn)redis在面試中還是比較常問的(筆主主Java方向)。所以查閱官方文檔以及他人造好的輪子,總結(jié)了一些redis面試和學(xué)習(xí)中你必須掌握的問題。...
摘要:也是自帶的一個(gè)基于線程池設(shè)計(jì)的定時(shí)任務(wù)類。其每個(gè)調(diào)度任務(wù)都會(huì)分配到線程池中的一個(gè)線程執(zhí)行,所以其任務(wù)是并發(fā)執(zhí)行的,互不影響。 原創(chuàng)不易,如需轉(zhuǎn)載,請(qǐng)注明出處https://www.cnblogs.com/baixianlong/p/10659045.html,否則將追究法律責(zé)任!??! 一、在JAVA開發(fā)領(lǐng)域,目前可以通過以下幾種方式進(jìn)行定時(shí)任務(wù) 1、單機(jī)部署模式 Timer:jdk中...
摘要:注解在類上為類提供一個(gè)全參的構(gòu)造方法,加了這個(gè)注解后,類中不提供默認(rèn)構(gòu)造方法了。這個(gè)注解用在類上,使用類中所有帶有注解的或者帶有修飾的成員變量生成對(duì)應(yīng)的構(gòu)造方法。 轉(zhuǎn)載請(qǐng)注明原創(chuàng)地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...
摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語(yǔ)言和等其他語(yǔ)言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語(yǔ)言和Java、python等其他語(yǔ)言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...
閱讀 2547·2021-11-24 09:39
閱讀 3444·2021-11-15 11:37
閱讀 2313·2021-10-08 10:04
閱讀 4012·2021-09-09 11:54
閱讀 1914·2021-08-18 10:24
閱讀 1118·2019-08-30 11:02
閱讀 1832·2019-08-29 18:45
閱讀 1694·2019-08-29 16:33