這篇文章主要講解了“怎么快速了解Java多線程”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么快速了解Java多線程”吧!
創(chuàng)新互聯(lián)建站是一家專業(yè)提供福山企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、HTML5建站、小程序制作等業(yè)務(wù)。10年已為福山眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進(jìn)行中。
線程
線程的概念:
線程(英語:Thread)是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實際運作單位。一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。在Unix System V及SunOS中也被稱為輕量進(jìn)程(Lightweight Processes),但輕量進(jìn)程更多指內(nèi)核線程(Kernel Thread),而把用戶線程(User Thread)稱為線程。
1.1 線程與進(jìn)程的區(qū)別
進(jìn)程:指在系統(tǒng)中正在運行的一個應(yīng)用程序;程序一旦運行就是進(jìn)程;進(jìn)程——資源分配的最小單位。
線程:系統(tǒng)分配處理器時間資源的基本單元,或者說進(jìn)程之內(nèi)獨立執(zhí)行的一個單元執(zhí)行流。線程——程序執(zhí)行的最小單位。
也就是,進(jìn)程可以包含多個線程,而線程是程序執(zhí)行的最小單位。
1.2 線程的狀態(tài)
NEW:線程剛創(chuàng)建
RUNNABLE: 在JVM中正在運行的線程,其中運行狀態(tài)可以有運行中RUNNING和READY兩種狀態(tài),由系統(tǒng)調(diào)度進(jìn)行狀態(tài)改變。
BLOCKED:線程處于阻塞狀態(tài),等待監(jiān)視鎖,可以重新進(jìn)行同步代碼塊中執(zhí)行
WAITING : 等待狀態(tài)
TIMED_WAITING: 調(diào)用sleep() join() wait()方法可能導(dǎo)致線程處于等待狀態(tài)
TERMINATED: 線程執(zhí)行完畢,已經(jīng)退出
1.3 Notify和Wait :
Notify和Wait 的作用
首先看源碼給出的解釋,這里翻譯了一下:
Notify:喚醒一個正在等待這個對象的線程監(jiān)控。如果有任何線程正在等待這個對象,那么它們中的一個被選擇被喚醒。選擇是任意的,發(fā)生在執(zhí)行的酌情權(quán)。一個線程等待一個對象通過調(diào)用一個{@code wait}方法進(jìn)行監(jiān)視。
Notify()需要在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須獲得該對象的對象級別鎖
Wait:導(dǎo)致當(dāng)前線程等待,直到另一個線程調(diào)用{@link java.lang.Object#notify()}方法或{@link java.lang.Object#notifyAll()}方法。
換句話說,這個方法的行為就像它簡單一樣執(zhí)行調(diào)用{@code wait(0)}。當(dāng)前線程必須擁有該對象的監(jiān)視器。
線程釋放此監(jiān)視器的所有權(quán),并等待另一個線程通知等待該對象的監(jiān)視器的線程,喚醒通過調(diào)用{@code notify}方法或{@code notifyAll}方法。然后線程等待,直到它可以重新取得監(jiān)視器的所有權(quán),然后繼續(xù)執(zhí)行。
Wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,它是Object類的方法,該方法用來將當(dāng)前線程置入預(yù)執(zhí)行隊列中,并且在Wait所在的代碼行處停止執(zhí)行,直到接到通知或被中斷為止。
在調(diào)用Wait方法之前,線程必須獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調(diào)用Wait方法。
Wait和Sleep的區(qū)別:
它們最大本質(zhì)的區(qū)別是,Sleep()不釋放同步鎖,Wait()釋放同步鎖。
還有用法的上的不同是:Sleep(milliseconds)可以用時間指定來使他自動醒過來,如果時間不到你只能調(diào)用Interreput()來強行打斷;Wait()可以用Notify()直接喚起。
這兩個方法來自不同的類分別是Thread和Object
最主要是Sleep方法沒有釋放鎖,而Wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
1.4 Thread.sleep() 和Thread.yield()的異同
相同 :Sleep()和yield()都會釋放CPU。
不同:Sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài),所以執(zhí)行Sleep()的線程在指定的時間內(nèi)肯定不會執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。Sleep()可使優(yōu)先級低的線程得到執(zhí)行的機(jī)會,當(dāng)然也可以讓同優(yōu)先級和高優(yōu)先級的線程有執(zhí)行的機(jī)會;yield()只能使同優(yōu)先級的線程有執(zhí)行的機(jī)會。
1.5 補充:死鎖的概念
死鎖:指兩個或兩個以上的進(jìn)程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
死鎖產(chǎn)生的四個必要條件(缺一不可):
互斥條件:顧名思義,線程對資源的訪問是排他性,當(dāng)該線程釋放資源后下一線程才可進(jìn)行占用。
請求和保持:簡單來說就是自己拿的不放手又等待新的資源到手。線程T1至少已經(jīng)保持了一個資源R1占用,但又提出對另一個資源R2請求,而此時,資源R2被其他線程T2占用,于是該線程T1也必須等待,但又對自己保持的資源R1不釋放。
不可剝奪:在沒有使用完資源時,其他線性不能進(jìn)行剝奪。
循環(huán)等待:一直等待對方線程釋放資源。
我們可以根據(jù)死鎖的四個必要條件破壞死鎖的形成。
1.6 補充:并發(fā)和并行的區(qū)別
并發(fā):是指在某個時間段內(nèi),多任務(wù)交替的執(zhí)行任務(wù)。當(dāng)有多個線程在操作時,把CPU運行時間劃分成若干個時間段,再將時間段分配給各個線程執(zhí)行。在一個時間段的線程代碼運行時,其它線程處于掛起狀。
并行:是指同一時刻同時處理多任務(wù)的能力。當(dāng)有多個線程在操作時,CPU同時處理這些線程請求的能力。
區(qū)別就在于CPU是否能同時處理所有任務(wù),并發(fā)不能,并行能。
1.7 補充:線程安全三要素
原子性:Atomic包、CAS算法、Synchronized、Lock。
可見性:Synchronized、Volatile(不能保證原子性)。
有序性:Happens-before規(guī)則。
1.8 補充:如何實現(xiàn)線程安全
互斥同步:Synchronized、Lock。
非阻塞同步:CAS。
無需同步的方案:如果一個方法本來就不涉及共享數(shù)據(jù),那它自然就無需任何同步操作去保證正確性。
1.9 補充:保證線程安全的機(jī)制:
Synchronized關(guān)鍵字
Lock
CAS、原子變量
ThreadLocl:簡單來說就是讓每個線程,對同一個變量,都有自己的獨有副本,每個線程實際訪問的對象都是自己的,自然也就不存在線程安全問題了。
Volatile
CopyOnWrite寫時復(fù)制
隨著CPU核心的增多以及互聯(lián)網(wǎng)迅速發(fā)展,單線程的程序處理速度越來越跟不上發(fā)展速度和大數(shù)據(jù)量的增長速度,多線程應(yīng)運而生,充分利用CPU資源的同時,極大提高了程序處理速度。
創(chuàng)建線程的方法
繼承Thread類:
public class ThreadCreateTest { public static void main(String[] args) { new MyThread().start(); } } class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()); } }
實現(xiàn)Runable接口:
public class RunableCreateTest { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); new Thread(runnable).start(); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()); } }
通過Callable和Future創(chuàng)建線程:
public class CallableCreateTest { public static void main(String[] args) throws Exception { // 將Callable包裝成FutureTask,F(xiàn)utureTask也是一種Runnable MyCallable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); // get方法會阻塞調(diào)用的線程 Integer sum = futureTask.get(); System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum); } } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting..."); int sum = 0; for (int i = 0; i <= 100000; i++) { sum += i; } Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover..."); return sum; } }
線程池方式創(chuàng)建:
實現(xiàn)Runnable接口這種方式更受歡迎,因為這不需要繼承Thread類。在應(yīng)用設(shè)計中已經(jīng)繼承了別的對象的情況下,這需要多繼承(而Java不支持多繼承,但可以多實現(xiàn)?。?,只能實現(xiàn)接口。同時,線程池也是非常高效的,很容易實現(xiàn)和使用。
實際開發(fā)中,阿里巴巴開發(fā)插件一直提倡使用線程池創(chuàng)建線程,原因在下方會解釋,所以上面的代碼我就只簡寫了一些Demo。
2.1 線程池創(chuàng)建線程
線程池,顧名思義,線程存放的地方。和數(shù)據(jù)庫連接池一樣,存在的目的就是為了較少系統(tǒng)開銷,主要由以下幾個特點:
降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗(主要)。
提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性。
Java提供四種線程池創(chuàng)建方式:
newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。
newScheduledThreadPool創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
通過源碼我們得知ThreadPoolExecutor繼承自AbstractExecutorService,而AbstractExecutorService實現(xiàn)了ExecutorService。
public class ThreadPoolExecutor extends AbstractExecutorService public abstract class AbstractExecutorService implements ExecutorService
2.2 ThreadPoolExecutor介紹
實際項目中,用的最多的就是ThreadPoolExecutor這個類,而《阿里巴巴Java開發(fā)手冊》中強制線程池不允許使用Executors去創(chuàng)建,而是通過New ThreadPoolExecutor實例的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風(fēng)險。
我們從ThreadPoolExecutor入手多線程創(chuàng)建方式,先看一下線程池創(chuàng)建的最全參數(shù)。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
參數(shù)說明如下:
corePoolSize:線程池的核心線程數(shù),即便線程池里沒有任何任務(wù),也會有corePoolSize個線程在候著等任務(wù)。
maximumPoolSize:最大線程數(shù),不管提交多少任務(wù),線程池里最多工作線程數(shù)就是maximumPoolSize。
keepAliveTime:線程的存活時間。當(dāng)線程池里的線程數(shù)大于corePoolSize時,如果等了keepAliveTime時長還沒有任務(wù)可執(zhí)行,則線程退出。
Unit:這個用來指定keepAliveTime的單位,比如秒:TimeUnit.SECONDS。
BlockingQueue:一個阻塞隊列,提交的任務(wù)將會被放到這個隊列里。
threadFactory:線程工廠,用來創(chuàng)建線程,主要是為了給線程起名字,默認(rèn)工廠的線程名字:pool-1-thread-3。
handler:拒絕策略,當(dāng)線程池里線程被耗盡,且隊列也滿了的時候會調(diào)用。
2.2.1BlockingQueue
對于BlockingQueue個人感覺還需要單獨拿出來說一下。
BlockingQueue:阻塞隊列,有先進(jìn)先出(注重公平性)和先進(jìn)后出(注重時效性)兩種,常見的有兩種阻塞隊列:ArrayBlockingQueue和LinkedBlockingQueue
隊列的數(shù)據(jù)結(jié)構(gòu)大致如圖:
隊列一端進(jìn)入,一端輸出。而當(dāng)隊列滿時,阻塞。BlockingQueue核心方法:1. 放入數(shù)據(jù)put2. 獲取數(shù)據(jù)take。常見的兩種Queue:
2.2.2 ArrayBlockingQueue
基于數(shù)組實現(xiàn),在ArrayBlockingQueue內(nèi)部,維護(hù)了一個定長數(shù)組,以便緩存隊列中的數(shù)據(jù)對象,這是一個常用的阻塞隊列,除了一個定長數(shù)組外,ArrayBlockingQueue內(nèi)部還保存著兩個整形變量,分別標(biāo)識著隊列的頭部和尾部在數(shù)組中的位置。
一段代碼來驗證一下:
package map; import java.util.concurrent.*; public class MyTestMap { // 定義阻塞隊列大小 private static final int maxSize = 5; public static void main(String[] args){ ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(maxSize); new Thread(new Productor(queue)).start(); new Thread(new Customer(queue)).start(); } } class Customer implements Runnable { private BlockingQueue<Integer> queue; Customer(BlockingQueue<Integer> queue) { this.queue = queue; } @Override public void run() { this.cusume(); } private void cusume() { while (true) { try { int count = (int) queue.take(); System.out.println("customer正在消費第" + count + "個商品==="); // 只是為了方便觀察輸出結(jié)果 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Productor implements Runnable { private BlockingQueue<Integer> queue; private int count = 1; Productor(BlockingQueue<Integer> queue) { this.queue = queue; } @Override public void run() { this.product(); } private void product() { while (true) { try { queue.put(count); System.out.println("生產(chǎn)者正在生產(chǎn)第" + count + "個商品"); count++; } catch (InterruptedException e) { e.printStackTrace(); } } } } //輸出如下 /** 生產(chǎn)者正在生產(chǎn)第1個商品 生產(chǎn)者正在生產(chǎn)第2個商品 生產(chǎn)者正在生產(chǎn)第3個商品 生產(chǎn)者正在生產(chǎn)第4個商品 生產(chǎn)者正在生產(chǎn)第5個商品 customer正在消費第1個商品=== */
2.2.3 LinkedBlockingQueue
基于鏈表的阻塞隊列,內(nèi)部也維護(hù)了一個數(shù)據(jù)緩沖隊列。需要我們注意的是如果構(gòu)造一個LinkedBlockingQueue對象,而沒有指定其容量大小。
LinkedBlockingQueue會默認(rèn)一個類似無限大小的容量(Integer.MAX_VALUE),這樣的話,如果生產(chǎn)者的速度一旦大于消費者的速度,也許還沒有等到隊列滿阻塞產(chǎn)生,系統(tǒng)內(nèi)存就有可能已被消耗殆盡了。
2.2.4 LinkedBlockingQueue和ArrayBlockingQueue的主要區(qū)別
ArrayBlockingQueue的初始化必須傳入隊列大小,LinkedBlockingQueue則可以不傳入。
ArrayBlockingQueue用一把鎖控制并發(fā),LinkedBlockingQueue倆把鎖控制并發(fā),鎖的細(xì)粒度更細(xì)。即前者生產(chǎn)者消費者進(jìn)出都是一把鎖,后者生產(chǎn)者生產(chǎn)進(jìn)入是一把鎖,消費者消費是另一把鎖。
ArrayBlockingQueue采用數(shù)組的方式存取,LinkedBlockingQueue用Node鏈表方式存取。
2.2.5handler拒絕策略
Java提供了4種丟棄處理的方法,當(dāng)然你也可以自己實現(xiàn),主要是要實現(xiàn)接口:RejectedExecutionHandler中的方法。
AbortPolicy:不處理,直接拋出異常。
CallerRunsPolicy:只用調(diào)用者所在線程來運行任務(wù),即提交任務(wù)的線程。
DiscardOldestPolicy:LRU策略,丟棄隊列里最近最久不使用的一個任務(wù),并執(zhí)行當(dāng)前任務(wù)。
DiscardPolicy:不處理,丟棄掉,不拋出異常。
2.2.6線程池五種狀態(tài)
private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
RUNNING:在這個狀態(tài)的線程池能判斷接受新提交的任務(wù),并且也能處理阻塞隊列中的任務(wù)。
SHUTDOWN:處于關(guān)閉的狀態(tài),該線程池不能接受新提交的任務(wù),但是可以處理阻塞隊列中已經(jīng)保存的任務(wù),在線程處于RUNNING狀態(tài),調(diào)用shutdown()方法能切換為該狀態(tài)。
STOP:線程池處于該狀態(tài)時既不能接受新的任務(wù)也不能處理阻塞隊列中的任務(wù),并且能中斷現(xiàn)在線程中的任務(wù)。當(dāng)線程處于RUNNING和SHUTDOWN狀態(tài),調(diào)用shutdownNow()方法就可以使線程變?yōu)樵摖顟B(tài)。
TIDYING:在SHUTDOWN狀態(tài)下阻塞隊列為空,且線程中的工作線程數(shù)量為0就會進(jìn)入該狀態(tài),當(dāng)在STOP狀態(tài)下時,只要線程中的工作線程數(shù)量為0就會進(jìn)入該狀態(tài)。
TERMINATED:在TIDYING狀態(tài)下調(diào)用terminated()方法就會進(jìn)入該狀態(tài)??梢哉J(rèn)為該狀態(tài)是最終的終止?fàn)顟B(tài)。
回到線程池創(chuàng)建ThreadPoolExecutor,我們了解了這些參數(shù),再來看看ThreadPoolExecutor的內(nèi)部工作原理:
判斷核心線程是否已滿,是進(jìn)入隊列,否:創(chuàng)建線程
判斷等待隊列是否已滿,是:查看線程池是否已滿,否:進(jìn)入等待隊列
查看線程池是否已滿,是:拒絕,否創(chuàng)建線程
2.3深入理解ThreadPoolExecutor
進(jìn)入Execute方法可以看到:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); //判斷當(dāng)前活躍線程數(shù)是否小于corePoolSize,如果小于,則調(diào)用addWorker創(chuàng)建線程執(zhí)行任務(wù) if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //如果不小于corePoolSize,則將任務(wù)添加到workQueue隊列。 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); } //如果放入workQueue失敗,則創(chuàng)建線程執(zhí)行任務(wù),如果這時創(chuàng)建線程失敗(當(dāng)前線程數(shù)不小于maximumPoolSize時),就會調(diào)用reject(內(nèi)部調(diào)用handler)拒絕接受任務(wù)。 else if (!addWorker(command, false)) reject(command); }
AddWorker方法:
創(chuàng)建Worker對象,同時也會實例化一個Thread對象。在創(chuàng)建Worker時會調(diào)用threadFactory來創(chuàng)建一個線程。
然后啟動這個線程。
2.3.1線程池中CTL屬性的作用是什么?
看源碼第一反應(yīng)就是這個CTL到底是個什么東東?有啥用?一番研究得出如下結(jié)論:
CTL屬性包含兩個概念:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static int ctlOf(int rs, int wc) { return rs | wc; }
runState:即rs 表明當(dāng)前線程池的狀態(tài),是否處于Running,Shutdown,Stop,Tidying。
workerCount:即wc表明當(dāng)前有效的線程數(shù)。
我們點擊workerCount即工作狀態(tài)記錄值,以RUNNING為例,RUNNING = -1 << COUNT_BITS;,即-1無符號左移COUNT_BITS位,進(jìn)一步我們得知COUNT_BITS位29,因為Integer位數(shù)為31位(2的五次方減一)
private static final int COUNT_BITS = Integer.SIZE - 3;
既然是29位那么就是Running的值為:
1110 0000 0000 0000 0000 0000 0000 0000 ||| 31~29位
那低28位呢,就是記錄當(dāng)前線程的總線數(shù)啦:
// Packing and unpacking ctl private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; }
從上述代碼可以看到workerCountOf這個函數(shù)傳入ctl之后,是通過CTL&CAPACITY操作來獲取當(dāng)前運行線程總數(shù)的。
也就是RunningState|WorkCount&CAPACITY,算出來的就是低28位的值。因為CAPACITY得到的就是高3位(29-31位)位0,低28位(0-28位)都是1,所以得到的就是ctl中低28位的值。
而runStateOf這個方法的話,算的就是RunningState|WorkCount&CAPACITY,高3位的值,因為CAPACITY是CAPACITY的取反,所以得到的就是高3位(29-31位)為1,低28位(0-28位)為0,所以通過&運算后,所得到的值就是高3為的值。
簡單來說就是ctl中是高3位作為狀態(tài)值,低28位作為線程總數(shù)值來進(jìn)行存儲。
2.3.2 shutdownNow和shutdown的區(qū)別
看源碼發(fā)現(xiàn)有兩種近乎一樣的方法,shutdownNow和shutdown,設(shè)計者這么設(shè)計自然是有它的道理,那么這兩個方法的區(qū)別在哪呢?
shutdown會把線程池的狀態(tài)改為SHUTDOWN,而shutdownNow把當(dāng)前線程池狀態(tài)改為STOP。
shutdown只會中斷所有空閑的線程,而shutdownNow會中斷所有的線程。
shutdown返回方法為空,會將當(dāng)前任務(wù)隊列中的所有任務(wù)執(zhí)行完畢;而shutdownNow把任務(wù)隊列中的所有任務(wù)都取出來返回。
2.3.3 線程復(fù)用原理
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); } }
就是任務(wù)在并不只執(zhí)行創(chuàng)建時指定的firstTask第一任務(wù),還會從任務(wù)隊列的中自己主動取任務(wù)執(zhí)行,而且是有或者無時間限定的阻塞等待,以保證線程的存活。
默認(rèn)的是不允許。
2.4 CountDownLatch和CyclicBarrier區(qū)別
countDownLatch是一個計數(shù)器,線程完成一個記錄一個,計數(shù)器遞減,只能只用一次。
CyclicBarrier的計數(shù)器更像一個閥門,需要所有線程都到達(dá),然后繼續(xù)執(zhí)行,計數(shù)器遞增,提供Reset功能,可以多次使用。
3. 多線程間通信的幾種方式
提及多線程又不得不提及多線程通信的機(jī)制。首先,要短信線程間通信的模型有兩種:共享內(nèi)存和消息傳遞,以下方式都是基本這兩種模型來實現(xiàn)的。我們來基本一道面試常見的題目來分析:
題目:有兩個線程A、B,A線程向一個集合里面依次添加元素"abc"字符串,一共添加十次,當(dāng)添加到第五次的時候,希望B線程能夠收到A線程的通知,然后B線程執(zhí)行相關(guān)的業(yè)務(wù)操作。
3.1使用volatile關(guān)鍵字
package thread; /** * * @author hxz * @description 多線程測試類 * @version 1.0 * @data 2020年2月15日 上午9:10:09 */ public class MyThreadTest { public static void main(String[] args) throws Exception { notifyThreadWithVolatile(); } /** * 定義一個測試 */ private static volatile boolean flag = false; /** * 計算I++,當(dāng)I==5時,通知線程B * @throws Exception */ private static void notifyThreadWithVolatile() throws Exception { Thread thc = new Thread("線程A"){ @Override public void run() { for (int i = 0; i < 10; i++) { if (i == 5) { flag = true; try { Thread.sleep(500L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } break; } System.out.println(Thread.currentThread().getName() + "====" + i); } } }; Thread thd = new Thread("線程B") { @Override public void run() { while (true) { // 防止偽喚醒 所以使用了while while (flag) { System.out.println(Thread.currentThread().getName() + "收到通知"); System.out.println("do something"); try { Thread.sleep(500L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ; } } } }; thd.start(); Thread.sleep(1000L); thc.start(); } }
感謝各位的閱讀,以上就是“怎么快速了解Java多線程”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么快速了解Java多線程這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
當(dāng)前標(biāo)題:怎么快速了解Java多線程
文章URL:http://www.rwnh.cn/article22/ippcjc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、定制開發(fā)、品牌網(wǎng)站設(shè)計、建站公司、定制網(wǎng)站、電子商務(wù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)