JavaSE(十一)——多线程
文章目錄
- 1. 進(jìn)程和線程
- 1.1 串行和并行
- 1.2 并發(fā)和并行
- 1.3 進(jìn)程和線程
- 1.4 線程的五種狀態(tài)
- 1.5 線程池
- 2. 線程對(duì)象
- 2.1 線程創(chuàng)建方式一:繼承Thread類
- 2.2 線程創(chuàng)建方法二:實(shí)現(xiàn)Runnable接口
- 2.3 線程的命名
- 2.4 線程優(yōu)先級(jí)
- 2.5 線程休眠:sleep方法
- 2.6 線程禮讓:yield方法
- 2.7 線程聯(lián)合:join方法
- 2.8 線程停止:stop方法
- 2.9 守護(hù)線程
- 3. 線程同步
- 3.1 線程沖突
- 3.2 同步方法與同步代碼塊
- 4. 線程死鎖
- 5. 線程協(xié)調(diào)
- 6. 高級(jí)并發(fā)對(duì)象
- 6.1 線程定義
- 6.2 線程同步:鎖對(duì)象
- 6.3 線程池
- 6.4 并發(fā)集合:BLockingQueue
- 6.5 靜態(tài)代理
- 6.6 Lambda表達(dá)式
1. 進(jìn)程和線程
1.1 串行和并行
串行:指多個(gè)任務(wù)時(shí),各個(gè)任務(wù)按順序執(zhí)行,完成一個(gè)之后才能進(jìn)行下一個(gè)。
并行:指多個(gè)任務(wù)可以同時(shí)執(zhí)行。異步是多個(gè)任務(wù)并行的前提。
1.2 并發(fā)和并行
并發(fā):在操作系統(tǒng)中,是指一個(gè)時(shí)間段中有幾個(gè)程序都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理機(jī)上運(yùn)行,但任一個(gè)時(shí)刻點(diǎn)上只有一個(gè)程序在處理機(jī)上運(yùn)行。其中兩種并發(fā)關(guān)系分別是同步和互斥,
- 互斥:進(jìn)程間相互排斥的使用臨界資源的現(xiàn)象,就叫互斥。
- 同步:進(jìn)程之間的關(guān)系不是相互排斥臨界資源的關(guān)系,而是相互依賴的關(guān)系。進(jìn)一步的說明:就是前一個(gè)進(jìn)程的輸出作為后一個(gè)進(jìn)程的輸入,當(dāng)?shù)谝粋€(gè)進(jìn)程沒有輸出時(shí)第二個(gè)進(jìn)程必須等待。具有同步關(guān)系的一組并發(fā)進(jìn)程相互發(fā)送的信息稱為消息或事件。
- 異步:異步和同步是相對(duì)的,同步就是順序執(zhí)行,執(zhí)行完一個(gè)再執(zhí)行下一個(gè),需要等待、協(xié)調(diào)運(yùn)行。異步就是彼此獨(dú)立,在等待某事件的過程中繼續(xù)做自己的事,不需要等待這一事件完成后再工作。線程就是實(shí)現(xiàn)異步的一個(gè)方式。
并行:在操作系統(tǒng)中,一組程序按獨(dú)立異步的速度執(zhí)行,無論從微觀還是宏觀,程序都是一起執(zhí)行的。
來個(gè)比喻:并發(fā)和并行的區(qū)別就是一個(gè)人同時(shí)吃三個(gè)饅頭和三個(gè)人同時(shí)吃三個(gè)饅頭;
1.3 進(jìn)程和線程
進(jìn)程:具有獨(dú)立的執(zhí)行環(huán)境和一套完整的私有基本運(yùn)行時(shí)資源,每個(gè)進(jìn)程都有自己的存儲(chǔ)空間。
線程:有時(shí)稱為輕量級(jí)進(jìn)程,創(chuàng)建新線程所需的資源少于創(chuàng)建新進(jìn)程的資源。
進(jìn)程與線程的區(qū)別:
- 進(jìn)程是資源分配最小單位,線程是程序執(zhí)行的最小單位;
- 進(jìn)程有自己獨(dú)立的地址空間,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)都會(huì)為其分配地址空間,建立數(shù)據(jù)表來維護(hù)代碼段、堆棧段和數(shù)據(jù)段,線程沒有獨(dú)立的地址空間,它使用相同的地址空間共享數(shù)據(jù);
- CPU切換一個(gè)線程比切換進(jìn)程花費(fèi)小;
- 創(chuàng)建一個(gè)線程比進(jìn)程開銷小;
- 線程占用的資源要?進(jìn)程少很多;
- 線程之間通信更方便,同一個(gè)進(jìn)程下,線程共享全局變量,靜態(tài)變量等數(shù)據(jù),進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行;(但多線程程序處理好同步與互斥是個(gè)難點(diǎn));
- 多進(jìn)程程序更安全,生命力更強(qiáng),一個(gè)進(jìn)程死掉不會(huì)對(duì)另一個(gè)進(jìn)程造成影響(源于有獨(dú)立的地址空間),多線程程序更不易維護(hù),一個(gè)線程死掉,整個(gè)進(jìn)程就死掉了(因?yàn)楣蚕淼刂房臻g);
- 進(jìn)程對(duì)資源保護(hù)要求高,開銷大,效率相對(duì)較低,線程資源保護(hù)要求不高,但開銷小,效率高,可頻繁切換。
線程狀態(tài)如下所示:
1.4 線程的五種狀態(tài)
(1)新建( new ):新創(chuàng)建了一個(gè)線程對(duì)象。
(2)可運(yùn)行( runnable ):線程對(duì)象創(chuàng)建后,其他線程(比如 main 線程)調(diào)用了該對(duì)象的 start ()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中,獲取 cpu 的使用權(quán) 。
(3)運(yùn)行( running ):可運(yùn)行狀態(tài)( runnable )的線程獲得了 cpu 時(shí)間片( timeslice ) , 執(zhí)行程序代碼。
(4)阻塞( block ):阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了 cpu 使用權(quán),如當(dāng)調(diào)用sleep()、wait()方法或同步鎖時(shí),線程進(jìn)入阻塞狀態(tài),不再往下執(zhí)行。阻塞事件解除后,線程重新進(jìn)入可運(yùn)行( runnable )狀態(tài),才有機(jī)會(huì)再次獲得 cpu 使用權(quán)轉(zhuǎn)到運(yùn)行( running )狀態(tài)。
(5)死亡( dead ):線程 run ()、 main () 方法執(zhí)行結(jié)束,或者因異常退出了 run ()方法,則 該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。
1.5 線程池
線程池顧名思義就是事先創(chuàng)建若干個(gè)可執(zhí)行的線程放入一個(gè)池(容器)中,需要的時(shí)候從池中獲取線程不用自行創(chuàng)建, 使用完畢不需要銷毀線程而是放回池中,從而減少創(chuàng)建和銷毀線程對(duì)象的開銷。
2. 線程對(duì)象
創(chuàng)建線程的三種方式:繼承Thread類,實(shí)現(xiàn)Runnable接口,實(shí)現(xiàn)Callable接口
2.1 線程創(chuàng)建方式一:繼承Thread類
步驟:
(1)自定義線程類繼承Thread類
(2)重寫run()方法,編寫線程執(zhí)行體
(3)創(chuàng)建線程對(duì)象,調(diào)用start()方法啟動(dòng)線程
2.2 線程創(chuàng)建方法二:實(shí)現(xiàn)Runnable接口
步驟:
(1)自定義一個(gè)類實(shí)現(xiàn)Runnable接口
(2)重寫run()方法,編寫線程執(zhí)行體
(3)創(chuàng)建線程對(duì)象,調(diào)用start()方法啟動(dòng)線程
注意:實(shí)現(xiàn) Runnable 接口這種方式更受歡迎,因?yàn)檫@不需要繼承 Thread 類。在應(yīng)用設(shè)計(jì)中已經(jīng)繼承了 別的對(duì)象的情況下,這需要多繼承(而 Java 不支持多繼承),只能實(shí)現(xiàn)接口。
- 多線程有兩種實(shí)現(xiàn)方法,分別是繼承Thread類與實(shí)現(xiàn)Runnable接口;兩者在啟動(dòng)線程時(shí)不同,繼承Thread類是子類對(duì)象.start(),而實(shí)現(xiàn)Runnable接口是傳入目標(biāo)對(duì)象+Thread對(duì)象.start()
- 同步的實(shí)現(xiàn)方面有兩種, 分別是 synchronized, wait 與 notify。
2.3 線程的命名
(1)Thread thread = new Thread(helloRunnable,“我是子線程1”);
案例:
(2)Thread thread = new Thread(helloRunnable);
thread.setName(“我是子線程1”);
案例:
2.4 線程優(yōu)先級(jí)
線程優(yōu)先級(jí)的范圍是1~10,默認(rèn)的優(yōu)先級(jí)是5,最高級(jí)是10。“高優(yōu)先級(jí)線程”被分配CPU的概率高于“低優(yōu)先級(jí)線程”。
//線程的優(yōu)先級(jí)用數(shù)字表示 Thread.MIN_PRIORITY=1; Thread.MAX_PRIORITY=10; Thread.NORM_PRIORITY=5;//獲取優(yōu)先級(jí) getPriority();//設(shè)置優(yōu)先級(jí)大小 setPriority(int xxx);實(shí)例:
2.5 線程休眠:sleep方法
Thread.sleep() 使當(dāng)前線程在指定時(shí)間段內(nèi)暫停執(zhí)行。
實(shí)例:
2.6 線程禮讓:yield方法
Thread.yield() 給當(dāng)前正處于運(yùn)行狀態(tài)下的線程一個(gè)提醒,告知它可以將資源禮讓給其他線程,但這僅僅是一種暗示,沒有任何一種機(jī)制保證當(dāng)前線程會(huì)將資源禮讓。
實(shí)例:
sleep()和yield()方法的區(qū)別:
-
sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);
-
yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì)。
2.7 線程聯(lián)合:join方法
join方法允許一個(gè)線程等待另一個(gè)線程的完成。如果t是Thread正在執(zhí)行其線程對(duì)象,t.join()導(dǎo)致當(dāng)前線程暫停執(zhí)行,直到 t 的線程終止。
類似于插隊(duì),如下列vip線程插隊(duì)到主線程
package Thread;public class TestJoin implements Runnable {@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println("我是vip線程--->"+i);}}public static void main(String[] args) throws InterruptedException {TestJoin testJoin=new TestJoin();Thread thread = new Thread(testJoin);thread.start();//主線程for (int i = 0; i < 1000; i++) {if(i==100){thread.join(); //vip線程插隊(duì)到主線程}System.out.println("我是主線程--->"+i);}} }運(yùn)行結(jié)果:
2.8 線程停止:stop方法
停止一個(gè)線程意味著在線程處理任務(wù)完成之前停掉正在做的操作,也就是放棄當(dāng)前操作。
推薦使用退出標(biāo)識(shí),使得線程正常退出,即當(dāng)run方法完成后進(jìn)程終止。
實(shí)例:
2.9 守護(hù)線程
- Java線程分為用戶線程和守護(hù)線程兩種;
- 用戶線程是系統(tǒng)的工作線程,它會(huì)完成這個(gè)程序要完成的業(yè)務(wù)員操作;
- 守護(hù)線程是一種特殊的線程,是系統(tǒng)的守護(hù)者,在后臺(tái)默默完成一些系統(tǒng)性的服務(wù),比如垃圾回收線程。
如果用戶線程全部結(jié)束,則意味著這個(gè)程序無事可做。守護(hù)線程要守護(hù)的對(duì)象已經(jīng)不存在了,那么整個(gè)應(yīng)用程序就結(jié)束了。
此處實(shí)例:龜兔賽跑
3. 線程同步
在多線程程序中,會(huì)出現(xiàn)多個(gè)線程搶占一個(gè)資源的情況,即出現(xiàn)并發(fā)現(xiàn)象,這時(shí)間有可能會(huì)造成沖突,也就是一個(gè)線程可能還沒來得及將更改的資源保存,另一個(gè)線程的更改就開始了。可能造成數(shù)據(jù)不一致。因此引入多線程同步,也就是說多個(gè)線程只能一個(gè)對(duì)共享的資源進(jìn)行更改,其他線程不能對(duì)數(shù)據(jù)進(jìn)行修改。
3.1 線程沖突
當(dāng)在不同線程中運(yùn)行作用于相同數(shù)據(jù)的兩個(gè)操作時(shí),就會(huì)發(fā)生干擾,這意味著這兩個(gè)操作由多個(gè)步驟組成,并且步驟順序重疊。
- 檢索的當(dāng)前值c
- 將檢索到的值加1
- 將增加的值存儲(chǔ)回c
此處實(shí)例:售票員們賣100張票
3.2 同步方法與同步代碼塊
synchronized用于解決同步問題,當(dāng)有多條線程同時(shí)訪問共享數(shù)據(jù)時(shí),如果進(jìn)行同步,就會(huì)發(fā)生錯(cuò)誤,Java提供的解決方案是:只要將操作共享數(shù)據(jù)的語句在某一時(shí)段讓一個(gè)線程執(zhí)行完,在執(zhí)行過程中,其他線程不能進(jìn)來執(zhí)行可以。解決這個(gè)問題。這里在用synchronized時(shí)會(huì)有兩種方式,一種是上面的同步方法,即用synchronized來修飾方法,另一種是提供的同步代碼塊。
//同步方法 public synchronized void method1(){}//同步代碼塊 public void method2(){synchronized (Obj){} //其中Obj稱為同步監(jiān)視器,可以是任何對(duì)象 }常見面試題:synchronized 和 java.util.concurrent.locks.Lock 的異同
- 主要相同點(diǎn):Lock 能完成 synchronized 所實(shí)現(xiàn)的所有功能。
- 主要不同點(diǎn):
(1)synchronized 是托管給 JVM 執(zhí)行的,lock 的鎖定是通過代碼實(shí)現(xiàn)的,它有比 synchronized 更精確的線程語義。
(2)synchronized 會(huì)自動(dòng)釋放鎖,而 Lock 一定要求程序員手工釋放 ,并且必須在 finally 語句中釋放。
(3)synchronized 既可以加在方法上,也可以加載特定代碼塊上,而Lock 需要顯示地指定起始位置和終止位置。
4. 線程死鎖
-
死鎖是指多個(gè)線程各自占有一些共享資源,并且相互等待其他線程占有的資源才能運(yùn)行,導(dǎo)致線程出現(xiàn)死鎖。
-
死鎖不僅僅在線程之間會(huì)發(fā)生,存在資源獨(dú)占的進(jìn)程之間同樣也可能出現(xiàn)死鎖。通常來說,我們大多是聚焦在多線程場(chǎng)景中的死鎖,指兩個(gè)或多個(gè)線程之間,由于互相持有對(duì)方需要的鎖,互相等待,而永久處于阻塞狀態(tài)。
-
使用多線程的時(shí)候,一種非常簡(jiǎn)單的避免死鎖的方式就是:指定獲取鎖的順序,并強(qiáng)制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會(huì)出現(xiàn)死鎖了。
5. 線程協(xié)調(diào)
-
wait()方法和notify()方法;
-
發(fā)生死鎖的條件
- 互斥條件
- 請(qǐng)求保持條件
- 不剝奪條件
- 循環(huán)等待條件
懶漢式單例模式:
- 在使用這個(gè)對(duì)象時(shí),才去查看這個(gè)對(duì)象是否創(chuàng)建。如果沒創(chuàng)建就馬上創(chuàng)建;如果已經(jīng)創(chuàng)建,就返回這個(gè)實(shí)例。
- 線程不安全,需要加上同步鎖,影響了程序執(zhí)行效率。
餓漢式單例模式:
- 在加載這個(gè)類的時(shí)候,就先創(chuàng)建好一個(gè)對(duì)象實(shí)例,等待調(diào)用。
- 天生線程安全,類加載的時(shí)候初始化一次對(duì)象,效率比懶漢式高
6. 高級(jí)并發(fā)對(duì)象
6.1 線程定義
- 實(shí)現(xiàn)Callable接口
前面兩種線程定義方式都有這兩個(gè)問題:
- 無法獲取子線程的返回值
- run方法不可以拋出異常
為了解決這兩個(gè)問題,我們就需要用到Callable這個(gè)接口了。
6.2 線程同步:鎖對(duì)象
同步代碼依賴于一種簡(jiǎn)單的可重入鎖。這種鎖易于使用,但有很多限制。java.util.concurrent.locks軟件包支持更復(fù)雜的鎖定習(xí)慣用法 。Lock對(duì)象的工作方式非常類似于同步代碼所使用的隱式鎖。與隱式鎖一樣,一次只能有一個(gè)線程擁有一個(gè)Lock對(duì)象。Lock對(duì)象還wait/notify通過其關(guān)聯(lián)的Condition對(duì)象支持一種機(jī)制 。
- reentrantLock.lock()
- reentrantLock.unlock()
6.3 線程池
線程池顧名思義就是事先創(chuàng)建若干個(gè)可執(zhí)行的線程放入一個(gè)池(容器)中,需要的時(shí)候從池中獲取線程不用自行創(chuàng)建, 使用完畢不需要銷毀線程而是放回池中,從而減少創(chuàng)建和銷毀線程對(duì)象的開銷。
Java中創(chuàng)建和銷毀一個(gè)線程是比較昂貴的操作,需要系統(tǒng)調(diào)用。頻繁創(chuàng)建和銷毀線程會(huì)影響系統(tǒng)性能。Java 通過 Executors 提供四種線程池,分別為:
- newCachedThreadPool 創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
- newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
- newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
- newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù), 保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。
6.4 并發(fā)集合:BLockingQueue
BlockingQueue實(shí)現(xiàn)被設(shè)計(jì)為主要用于生產(chǎn)者-消費(fèi)者隊(duì)列,如果BlockingQueue是空的,從BlockingQueue取東西的操作將會(huì)被阻斷進(jìn)入等待狀態(tài),直到BlockingQueue進(jìn)了東西才會(huì)被喚醒。同樣,如果BlockingQueue是滿的,任何試圖往里存東西的操作也會(huì)被阻斷進(jìn)入等待狀態(tài),直到BlockingQueue里有空間時(shí)才會(huì)被喚醒。
import java.util.concurrent.BlockingQueue; public class Consumer implements Runnable {BlockingQueue<Product> blockingQueue;public Consumer(BlockingQueue<Product> blockingQueue) {this.blockingQueue = blockingQueue;}@Overridepublic void run() {try {while (true) {Product product = blockingQueue.take();System.out.println("消費(fèi)產(chǎn)品: " + product.getName());Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}} }6.5 靜態(tài)代理
真實(shí)對(duì)象和代理對(duì)象都要實(shí)現(xiàn)同一個(gè)接口,代理對(duì)象要代理真實(shí)角色。
好處:代理對(duì)象可以做很多真實(shí)對(duì)象做不了的事情,真實(shí)對(duì)象只專注做自己的事情,如婚慶公司和結(jié)婚人之間的關(guān)系
6.6 Lambda表達(dá)式
- Lambda 表達(dá)式,也可稱為閉包,它是推動(dòng) Java 8 發(fā)布的最重要新特性。
- Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。
- 使用 Lambda 表達(dá)式可以使代碼變的更加簡(jiǎn)潔緊湊。
Lambda表達(dá)式的重要特征:
- 可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識(shí)別參數(shù)值。
- 可選的參數(shù)圓括號(hào):一個(gè)參數(shù)無需定義圓括號(hào),但多個(gè)參數(shù)需要定義圓括號(hào)。
- 可選的大括號(hào):如果主體包含了一個(gè)語句,就不需要使用大括號(hào)。
- 可選的返回關(guān)鍵字:如果主體只有一個(gè)表達(dá)式返回值則編譯器會(huì)自動(dòng)返回值,大括號(hào)需要指定明表達(dá)式返回了一個(gè)數(shù)值。
Lambda 表達(dá)式的簡(jiǎn)單例子:
// 1. 不需要參數(shù),返回值為 5 () -> 5 // 2. 接收一個(gè)參數(shù)(數(shù)字類型),返回其2倍的值 x -> 2 * x // 3. 接受2個(gè)參數(shù)(數(shù)字),并返回他們的差值 (x, y) -> x – y // 4. 接收2個(gè)int型整數(shù),返回他們的和 (int x, int y) -> x + y // 5. 接受一個(gè) string 對(duì)象,并在控制臺(tái)打印,不返回任何值(看起來像是返回void) (String s) -> System.out.print(s)總結(jié)
以上是生活随笔為你收集整理的JavaSE(十一)——多线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaSE(十)——set和map集合
- 下一篇: JavaSE(十二)——AWT