面試題-關於Java執行緒池一篇文章就夠了
在Java面試中,執行緒池相關知識,雖不能說是必問提,但出現的頻次也是非常高的。同時又鑑於公眾號“程式新視界”的讀者後臺留言讓寫一篇關於Java執行緒池的文章,於是就有本篇內容,本篇將基於Java執行緒池的原理、實現以及相關原始碼進行講解等。
什麼是執行緒池
執行緒池是一種多執行緒處理形式,處理過程中將任務提交到執行緒池,任務的執行交由執行緒池來管理。
為了充分利用CPU多核資源,應用都會採用多執行緒並行/併發計算,最大限度的利用多核提升應用程式效能。
試想一下,如果每個請求都執行一遍建立執行緒、執行任務、銷燬執行緒,那麼對伺服器資源將是一種浪費。在高併發的情況下,甚至會耗盡伺服器資源。
執行緒池的主要作用有兩個:不同請求之間重複利用執行緒,無需頻繁的建立和銷燬執行緒,降低系統開銷和控制執行緒數量上限,避免建立過多的執行緒耗盡程序記憶體空間,同時減少執行緒上下文切換次數。
常見面試題
- 說說Java執行緒池的好處及實現的原理?
- Java提供執行緒池各個引數的作用,如何進行的?
- 根據執行緒池內部機制,當提交新任務時,有哪些異常要考慮?
- 執行緒池都有哪幾種工作佇列?
- 使用無界佇列的執行緒池會導致記憶體飆升嗎?
- 說說幾種常見的執行緒池及使用場景?
執行緒池的建立與使用
在JDK5版本中增加了內建執行緒池實現ThreadPoolExecutor,同時提供了Executors來建立不同型別的執行緒池。Executors中提供了以下常見的執行緒池建立方法:
- newSingleThreadExecutor:一個單執行緒的執行緒池。如果因異常結束,會再建立一個新的,保證按照提交順序執行。
- newFixedThreadPool:建立固定大小的執行緒池。根據提交的任務逐個增加執行緒,直到最大值保持不變。如果因異常結束,會新建立一個執行緒補充。
- newCachedThreadPool:建立一個可快取的執行緒池。會根據任務自動新增或回收執行緒。
- newScheduledThreadPool:支援定時以及週期性執行任務的需求。
- newWorkStealingPool:JDK8新增,根據所需的並行層次來動態建立和關閉執行緒,通過使用多個佇列減少競爭,底層使用ForkJoinPool來實現。優勢在於可以充分利用多CPU,把一個任務拆分成多個“小任務”,放到多個處理器核心上並行執行;當多個“小任務”執行完成之後,再將這些執行結果合併起來即可。
雖然在JDK中提供Executors類來支援以上型別的執行緒池建立,但通常情況下不建議開發人員直接使用(見《阿里巴巴java開發規範》)。
執行緒池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
Executors部分方法的弊端:
- newFixedThreadPool和newSingleThreadExecutor主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至OOM。
- newCachedThreadPool和newScheduledThreadPool:主要問題是執行緒數最大數是Integer.MAX_VALUE,可能會建立數量非常多的執行緒,甚至OOM。
同時,阿里巴巴java開發規範中推薦了3種執行緒池建立方式。
方式一,引入commons-lang3包。
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
方式二,引入com.google.guava包。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
方式三,spring配置執行緒池方式:自定義執行緒工廠bean需要實現ThreadFactory,可參考該介面的其它預設實現類,使用方式直接注入bean,呼叫execute(Runnable task)方法即可。
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
// in code
userThreadPool.execute(thread);
ThreadPoolExecutor的構造方法
除了以上推薦的建立執行緒池的方法,還可以通過ThreadPoolExecutor的構造方法,直接建立執行緒池。本質上來講,以上方法最終也是建立了ThreadPoolExecutor物件,然後堆積進行包裝處理。
ThreadPoolExecutor提供了多個構造方法,我們最終都呼叫的構造方法來進行說明。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 省略程式碼
}
核心引數作用解析如下:
- corePoolSize:執行緒池核心執行緒數最大值。
- maximumPoolSize:執行緒池最大執行緒數大小。
- keepAliveTime:執行緒池中非核心執行緒空閒的存活時間大小。
- unit:執行緒空閒存活時間單位。
- workQueue:存放任務的阻塞佇列。
- threadFactory:建立新執行緒的工廠,所有執行緒都是通過該工廠建立的,有預設實現。
- handler:執行緒池的拒絕策略。
程池的拒絕策略
構造方法的中最後的引數RejectedExecutionHandler用於指定執行緒池的拒絕策略。當請求任務不斷的過來,而系統此時又處理不過來的時候,我們就需要採取對應的策略是拒絕服務。
預設有四種類型:
- AbortPolicy策略:該策略會直接丟擲異常,阻止系統正常工作。
- CallerRunsPolicy策略:只要執行緒池未關閉,該策略直接在呼叫者執行緒中,運行當前的被丟棄的任務。
- DiscardOleddestPolicy策略:該策略將丟棄最老的一個請求,也就是即將被執行的任務,並嘗試再次提交當前任務。
- DiscardPolicy策略:該策略默默的丟棄無法處理的任務,不予任何處理。
當然,除了預設的4種策略之外,還可以根據業務需求自定義拒絕策略。通過實現RejectedExecutionHandler介面,在建立ThreadPoolExecutor物件時作為引數傳入即可。
在spring-integration-core中便自定義了CallerBlocksPolicy,相關程式碼如下:
public class CallerBlocksPolicy implements RejectedExecutionHandler {
private static final Log logger = LogFactory.getLog(CallerBlocksPolicy.class);
private final long maxWait;
public CallerBlocksPolicy(long maxWait) {
this.maxWait = maxWait;
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
try {
BlockingQueue<Runnable> queue = executor.getQueue();
if (logger.isDebugEnabled()) {
logger.debug("Attempting to queue task execution for " + this.maxWait + " milliseconds");
}
if (!queue.offer(r, this.maxWait, TimeUnit.MILLISECONDS)) {
throw new RejectedExecutionException("Max wait time expired to queue task");
} else {
if (logger.isDebugEnabled()) {
logger.debug("Task execution queued");
}
}
} catch (InterruptedException var4) {
Thread.currentThread().interrupt();
throw new RejectedExecutionException("Interrupted", var4);
}
} else {
throw new RejectedExecutionException("Executor has been shut down");
}
}
}
執行緒池的執行
建立完成ThreadPoolExecutor之後,當向執行緒池提交任務時,通常使用execute方法。execute方法的執行流程圖如下:
- 如果執行緒池中存活的核心執行緒數小於執行緒數corePoolSize時,執行緒池會建立一個核心執行緒去處理提交的任務。
- 如果執行緒池核心執行緒數已滿,即執行緒數已經等於corePoolSize,一個新提交的任務,會被放進任務佇列workQueue排隊等待執行。
- 當執行緒池裡面存活的執行緒數已經等於corePoolSize了,並且任務佇列workQueue也滿,判斷執行緒數是否達到maximumPoolSize,即最大執行緒數是否已滿,如果沒到達,建立一個非核心執行緒執行提交的任務。
- 如果當前的執行緒數達到了maximumPoolSize,還有新的任務過來的話,直接採用拒絕策略處理。
原始碼分析
下面看一下JDK8中ThreadPoolExecutor中execute方法的原始碼實現:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 執行緒池本身的狀態跟worker數量使用同一個變數ctl來維護
int c = ctl.get();
// 通過位運算得出當然執行緒池中的worker數量與構造引數corePoolSize進行比較
if (workerCountOf(c) < corePoolSize) {
// 如果小於corePoolSize,則直接新增一個worker,並把當然使用者提交的任務command作為引數,如果成功則返回。
if (addWorker(command, true))
return;
// 如果失敗,則獲取最新的執行緒池資料
c = ctl.get();
}
// 如果執行緒池仍在執行,則把任務放到阻塞佇列中等待執行。
if (isRunning(c) && workQueue.offer(command)) {
// 這裡的recheck思路是為了處理併發問題
int recheck = ctl.get();
// 當任務成功放入佇列時,如果recheck發現執行緒池已經不再運行了則從佇列中把任務刪除
if (! isRunning(recheck) && remove(command))
//刪除成功以後,會呼叫構造引數傳入的拒絕策略。
reject(command);
// 如果worker的數量為0(此時佇列中可能有任務沒有執行),則新建一個worker(由於此時新建woker的目的是執行佇列中堆積的任務,
// 因此入參沒有執行任務,詳細邏輯後面會詳細分析addWorker方法)。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果前面的新增woker,放入佇列都失敗,則會繼續新增worker,此時執行緒池的狀態是woker數量達到corePoolSize,阻塞佇列任務已滿
// 只能基於maximumPoolSize引數新建woker
else if (!addWorker(command, false))
// 如果基於maximumPoolSize新建woker失敗,此時是執行緒池中執行緒數已達到上限,佇列已滿,則呼叫構造引數中傳入的拒絕策略
reject(command);
}
下面再看在上述程式碼中呼叫的addWorker方法的原始碼實現及解析:
private boolean addWorker(Runnable firstTask, boolean core) {
// 這裡有一段基於CAS+死迴圈實現的關於執行緒池狀態,執行緒數量的校驗與更新邏輯就先忽略了,重點看主流程。
//...
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 把指定任務作為引數新建一個worker執行緒
w = new Worker(firstTask);
// 這裡是重點w.thread是通過執行緒池建構函式引數threadFactory生成的woker物件
// 也就是說這個變數t就是代表woker執行緒。絕對不是使用者提交的執行緒任務firstTask。
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 加鎖之後仍舊是判斷執行緒池狀態等一些校驗邏輯。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// 把新建的woker執行緒放入集合儲存,這裡使用的是HashSet
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 然後啟動woker執行緒
// 該變數t代表woker執行緒,會呼叫woker的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 如果woker啟動失敗,則進行一些善後工作,比如說修改當前woker數量等
addWorkerFailed(w);
}
return workerStarted;
}
addWorker方法主要做的工作就是新建一個Woker執行緒,加入到woker集合中。在上述方法中會呼叫到Worker類的run方法,並最終執行了runWorker方法。
// Woker類實現了Runnable介面
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// task就是Woker建構函式入參指定的任務,即使用者提交的任務
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//一般情況下,task都不會為空(特殊情況上面註釋中也說明了),因此會直接進入迴圈體中
//這裡getTask方法是要重點說明的,它的實現跟我們構造引數設定存活時間有關
//我們都知道構造引數設定的時間代表了執行緒池中的執行緒,即woker執行緒的存活時間,如果到期則回收woker執行緒,這個邏輯的實現就在getTask中。
//來不及執行的任務,執行緒池會放入一個阻塞佇列,getTask方法就是去阻塞佇列中取任務,使用者設定的存活時間,就是
//從這個阻塞佇列中取任務等待的最大時間,如果getTask返回null,意思就是woker等待了指定時間仍然沒有
//取到任務,此時就會跳過迴圈體,進入woker執行緒的銷燬邏輯。
while (task != null || (task = getTask()) != null) {
w.lock();
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 {
//這裡設為null,也就是迴圈體再執行的時候會呼叫getTask方法
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//當指定任務執行完成,阻塞佇列中也取不到可執行任務時,會進入這裡,做一些善後工作,比如在corePoolSize跟maximumPoolSize之間的woker會進行回收
processWorkerExit(w, completedAbruptly);
}
}
woker執行緒的執行流程就是首先執行初始化時分配給的任務,執行完成以後會嘗試從阻塞佇列中獲取可執行的任務,如果指定時間內仍然沒有任務可以執行,則進入銷燬邏輯。這裡只會回收corePoolSize與maximumPoolSize直接的那部分woker。
execute與submit的區別
執行任務除了可以使用execute方法還可以使用submit方法。它們的主要區別是:execute適用於不需要關注返回值的場景,submit方法適用於需要關注返回值的場景。
異常處理
當執行任務時發生異常,那麼該怎麼處理呢?首先看當Thread執行緒異常如何處理。
在任務中通過try...catch是可以捕獲異常並進行處理的,如下程式碼:
Thread t = new Thread(() -> {
try {
System.out.println(1 / 0);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
});
t.start();
如果很多執行緒任務預設的異常處理機制都是相同的,可以通過Thread類的UncaughtExceptionHandler來設定執行緒預設的異常處理機制。
實現UncaughtExceptionHandler介面,並呼叫Thread#setUncaughtExceptionHandler(UncaughtExceptionHandler)方法。如果想設定為全域性預設異常處理機制,則可呼叫Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)方法。
ThreadGroup預設提供了異常處理機制如下:
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);
}
}
}
ThreadPoolExecutor的異常處理機制與Thread是一樣的。同時,ThreadPoolExecutor提供了uncaughtExceptionHandler方法來設定異常處理。如下示例:
public class ThreadPool {
public static void main(String[] args) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d")
.setUncaughtExceptionHandler(new LogUncaughtExceptionHandler())
.build();
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(() -> {
throw new RuntimeException("測試異常");
});
pool.shutdown();
}
static class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("列印LogUncaughtExceptionHandler中獲得的異常資訊:" + e.getMessage());
}
}
}
但需要注意的是使用UncaughtExceptionHandler的方法只適用於execute方法執行的任務,而對submit方法是無效。submit執行的任務,可以通過返回的Future物件的get方法接收丟擲的異常,再進行處理。這也算是execute方法與submit方法的差別之一。
執行緒池中常見的佇列
執行緒池有以下工作佇列:
- ArrayBlockingQueue:有界佇列,是一個用陣列實現的有界阻塞佇列,按FIFO排序量。
- LinkedBlockingQueue:可設定容量佇列,基於連結串列結構的阻塞佇列,按FIFO排序任務,容量可以選擇進行設定,不設定的話,將是一個無邊界的阻塞佇列,最大長度為Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQuene;newFixedThreadPool執行緒池使用了這個佇列。
- DelayQueue:延遲佇列,是一個任務定時週期的延遲執行的佇列。根據指定的執行時間從小到大排序,否則根據插入到佇列的先後排序。newScheduledThreadPool執行緒池使用了這個佇列。
- PriorityBlockingQueue:優先順序佇列,是具有優先順序的無界阻塞佇列。
- SynchronousQueue:同步佇列,一個不儲存元素的阻塞佇列,每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene,newCachedThreadPool執行緒池使用了這個佇列。
關閉執行緒池
關閉執行緒池可以呼叫shutdownNow和shutdown兩個方法來實現。
shutdownNow:對正在執行的任務全部發出interrupt(),停止執行,對還未開始執行的任務全部取消,並且返回還沒開始的任務列表。
shutdown:當我們呼叫shutdown後,執行緒池將不再接受新的任務,但也不會去強制終止已經提交或者正在執行中的任務。
參考文章:
https://www.jianshu.com/p/5df6e38e4362
https://juejin.im/post/5d1882b1f265da1ba84aa676
原文連結:《面試題-關於Java執行緒池一篇文章就夠了》
相關推薦
面試題-關於Java執行緒池一篇文章就夠了
在Java面試中,執行緒池相關知識,雖不能說是必問提,但出現的頻次也是非常高的。同時又鑑於公眾號“程式新視界”的讀者後臺留言讓寫一篇關於Java執行緒池的文章,於是就有本篇內容,本篇將基於Java執行緒池的原理、實現以及相關原始碼進行講解等。 什麼是執行緒池 執行緒池是一種多執行緒處理形式,處理過程中將任務提
有C/C++/Java基礎的,學習Python一篇文章就夠了
我在學校裡學習的是C、C++、C#,實習的時候公司裡用的都是Java,於是又自學了java。用我領導的一句話說,“C++轉Java幾天時間就夠了”。最近在學習深度學習演算法和自然語言處理的時候,很多演算法都是用python寫的,於是又下定決定學習了python。
徹底理解Netty,這一篇文章就夠了
Netty到底是什麼 從HTTP說起 有了Netty,你可以實現自己的HTTP伺服器,FTP伺服器,UDP伺服器,RPC伺服器,WebSocket伺服器,Redis的Proxy伺服器,MySQL的Proxy伺服器等等。 我們回顧一下傳統的HTTP伺服器的原理 &n
Android 沉浸式全面詳解(這一篇文章就夠了)
方法一:通過設定Theme主題設定狀態列透明 因為 API21 之後(也就是 android 5.0 之後)的狀態列,會預設覆蓋一層半透明遮罩。且為了保持4.4以前系統正常使用,故需要三份 style 檔案,即預設的values(不設定狀態列透明)、values-v19、v
Ubuntu16.04下安裝配置Android studio,sdk,gradle——一篇文章就夠了系列
64位ubuntu16.04完美安裝配置jdk、sdk、Android studio 一、下載安裝jdk、sdk、Android studio 注意:我把JDK安裝在了/opt資料夾下面,/opt資料夾和/usr資料夾同樣都是常用做安裝軟體的資料夾,不同的是/usr是
Redis資料庫看這一篇文章就夠了
前言 redis作為nosql家族中非常熱門的一員,也是被大型網際網路公司所青睞,無論你是開發、測試或者運維,學習掌握它總會為你的職業生涯增色添彩。 當然,你或多或少已經瞭解redis,但是你是否瞭解其中的某些細節,本片文章將詳細介紹redis基礎,後續也會介紹其高階部分
TensorFlow深度學習,一篇文章就夠了
作者: 陳迪豪,就職小米科技,深度學習工程師,TensorFlow程式碼提交者。 TensorFlow深度學習框架 Google不僅是大資料和雲端計算的領導者,在機器學習和深度學習上也有很好的實踐和積累,在2015年年底開源了內部使用的深度學習框架TensorFlow。 與
前端幹活系列----入門webpack這一篇文章就夠了
博主學習webpack,並不是因為專案需要,而是因為webpack現在比較流行,所以想學習一下,順便看看對現在的專案是否有用。最後經過思考感覺對當前專案的作用並不大,不過總結了一下webpack適合使用的幾個場景 1.前後端分離的專案,像vue,angular
Android:學習AIDL,這一篇文章就夠了(下)
前言 上一篇博文介紹了關於AIDL是什麼,為什麼我們需要AIDL,AIDL的語法以及如何使用AIDL等方面的知識,這一篇博文將順著上一篇的思路往下走,接著介紹關於AIDL的一些更加深入的知識。強烈建議大家在看這篇博文之前先看一下上一篇博文: 注:文中所有程式
【線性代數】矩陣、向量、行列式、特徵值與特徵向量(掌握這些概念一篇文章就夠了)
在數學領域中,線性代數是一門十分有魅力的學科,首先,它不難學;其次,它能廣泛應用於現實生活中;另外,在機器學習越來越被重視的現在,線性代數也能算得上是一個優秀程式設計師的基本素養吧? 一、線性代數的入門知識 很多人在大學學習線性代數時,國內
Android:學習AIDL,這一篇文章就夠了(上)
前言 在決定用這個標題之前甚是忐忑,主要是擔心自己對AIDL的理解不夠深入,到時候大家看了之後說——你這是什麼玩意兒,就這麼點東西就敢說夠了?簡直是坐井觀天不知所謂——那樣就很尷尬了。不過又轉念一想,我輩年輕人自當有一種一往無前的銳氣,標題大氣一點豈不更好?並且大家都是
徹底理解分散式Netty,這一篇文章就夠了!
一、Netty到底是什麼 1、從HTTP說起 有了Netty,你可以實現自己的HTTP伺服器、FTP伺服器、UDP伺服器、RPC伺服器、WebSocket伺服器、Redis的Proxy伺服器、MySQL的Proxy伺服器等等。 我們回顧一下傳統的HTTP伺服器的原理
express中介軟體,一篇文章就夠了
回到目錄 底層:http模組 express目前是最流行的基於Node.js的web開發框架,express框架建立在內建的http模組上, var http = require('http') var app = http.createServer(funct
Activity、View、Window的理解一篇文章就夠了
要了解這三者之間的關係,我們帶著問題通過分析原始碼一步一步來揭開它們的神祕面紗! 文章有點長,首先要理解Activity、View、Window,我提出了一些問題,這篇文章可以解答如下問題: 1、為什麼要設計Activity、View、Window? 2、
關於Mysql的事務和鎖 看這一篇文章就夠了
鎖共享讀鎖(S鎖)和 排他寫鎖(X鎖)行鎖與表鎖innodb用的是行級鎖,相對於表鎖來說效能開銷會更大。雖然叫做行級鎖,但不表示他只鎖住修改的行記錄,即使找不到行記錄,他也會產生鎖。innodb 是根據掃描範圍來鎖定行記錄,如果有索引,那麼只會鎖定索引的覆蓋範圍,如果找不到索
企業公有云服務的構建“捷徑”,看這一篇文章就夠了
最近在上海舉辦的2018世界人工智慧大會,可謂是大佬雲集,金句頻出。在成為業內焦點的同時,也再次
想了解資料庫安全?看這一篇文章就夠了!
浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>
想了解數據庫安全?看這一篇文章就夠了!
pri 驗收測試 時間 災難 其中 嚴重 誤操作 了解 雲技術 本文由雲+社區發表 作者:騰訊雲數據庫 互聯網時代,人與人、人與社會交互過程中產生的行為數據、畫像數據、信息數據等正在呈指數級增長,同時數據的價值和重要性不言而喻。數據庫作為數據的載體,產品和技術也越來
關於Python編碼這一篇文章就夠了
概述 在使用Python或者其他的程式語言,都會多多少少遇到編碼錯誤,處理起來非常痛苦。在Stack Overflow和其他的程式
學習Canvas這一篇文章就夠了
一、canvas簡介 <canvas> 是 HTML5 新增的,一個可以使用指令碼(通常為JavaScript)在其中繪製圖像的 HTML 元素。它可以用來製作照片集或者製作簡單(也不是那麼簡單)的動畫,甚至可以進行實時視訊處理和渲染。 它最初由蘋果內部使用自己MacOS X WebKit推出