多線程基礎(chǔ)撿漏

以前學習拿出來記錄下,后面分享大數(shù)據(jù)相關(guān)的技術(shù)

一、程序、進程、線程

1. 程序(program)

概念:是為完成特定任務(wù)、用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼。

2. 進程(process)

概念:程序的一次執(zhí)行過程,或是正在運行的一個程序。 說明:進程作為資源分配的單位,系統(tǒng)在運行時會為每個進程分配不同的內(nèi)存區(qū)域

3. 線程(thread)

概念:進程可進一步細化為線程,是一個程序內(nèi)部的一條執(zhí)行路徑。 說明:線程作為調(diào)度和執(zhí)行的單位,每個線程擁獨立的運行棧和程序計數(shù)器(pc),線程切換的開銷小。

內(nèi)存結(jié)構(gòu):

進程可以細化為多個線程。 每個線程,擁有自己獨立的:棧、程序計數(shù)器 多個線程,共享同一個進程中的結(jié)構(gòu):方法區(qū)、堆。

二、并行與并發(fā)

1. 單核CPU與多核CPU

單核CPU,其實是一種假的多線程,因為在一個時間單元內(nèi),也只能執(zhí)行一個線程的任務(wù)。涉及到CPU處理線程的方式,CPU在單位時間(也就是說一個時間片內(nèi))內(nèi)只能處理一個線程,于是就將其他的線程設(shè)置為阻塞狀態(tài),加入到阻塞隊列中,等到處理完成當前線程后從就緒隊列中取出新的線程進行處理,由于切換和處理時間很快用戶感知不到于是用戶便認為CPU在同一時間內(nèi)處理多個線程。

多核CPU,才能更好的發(fā)揮多線程的效率。(現(xiàn)在的服務(wù)器都是多核的)

一個Java應(yīng)用程序java.exe,其實至少三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當然如果發(fā)生異常,會影響主線程。

2. 并行與并發(fā)的理解

并行:多個CPU同時執(zhí)行多個任務(wù)。比如:多個人同時做不同的事。

并發(fā):一個CPU(采用時間片)同時執(zhí)行多個任務(wù)。比如:秒殺、多個人做同一件事

為什么要使用多線程?

當我們在進行商品搶購的時候,在支付按鈕上總是有個計時器在進行倒計時,但是我們此時仍然可以進行商品信息的查看,這個計時器和我們?yōu)g覽商品信息的線程是同時進行的,這樣也就實現(xiàn)了搶購場景,增加了用戶的體驗。

多線程程序的優(yōu)點:

提高應(yīng)用程序的響應(yīng)。對圖形化界面更有意義,可增強用戶體驗。

提高計算機系統(tǒng)CPU的利用率。

應(yīng)用的場景

程序需要同時執(zhí)行兩個或多個任務(wù)。

程序需要實現(xiàn)一些需要等待的任務(wù)時,如用戶輸入、文件讀寫操作、網(wǎng)絡(luò)操作、搜索等

三、Thread類

Java語言的JVM允許程序運行多個線程,它通過 java. lang.Thread類來體現(xiàn)

1. Thread類的特性

每個線程都是通過某個特定 Thread對象的run(方法來完成操作的,經(jīng)常把run()方法的主體稱為線程體 通過該 Thread對象的 start(方法來啟動這個線程,而非直接調(diào)用run

2. 構(gòu)造器:

Thread():創(chuàng)建新的 Thread對象

Thread(String threadName):創(chuàng)建線程并指定線程實例名

Thread(Runnable target):指定創(chuàng)建線程的目標對象,它實現(xiàn)了 Runnable接口中的run方法

Thread(Runnable target, String name):創(chuàng)建新的 Thread對象

3. 創(chuàng)建多線程的兩種方式

3.1. 方式一繼承Thread類的方式:

創(chuàng)建一個繼承于Thread類的子類

重寫Thread類的run() --> 將此線程執(zhí)行的操作聲明在run()中

創(chuàng)建Thread類的子類的對象

通過此對象調(diào)用start():①啟動當前線程 ② 調(diào)用當前線程的run()

注意點:

我們啟動一個線程,必須調(diào)用start(),不能調(diào)用run()的方式啟動線程。 如果再啟動一個線程,必須重新創(chuàng)建一個Thread子類的對象,調(diào)用此對象的start().(注意后面的點)

如果自己手動調(diào)用run()方法,那么就只是普通方法,沒有啟動多線程模式

run(方法由JVM調(diào)用,什么時候調(diào)用,執(zhí)行的過程控制都有操作系統(tǒng)的CPU調(diào)度決定。

想要啟動多線程,必須調(diào)用 start方法。

一個線程對象只能調(diào)用一次 start()方法啟動,如果重復調(diào)用了,則將拋出異?!發(fā)llegalThreadStateException”.

代碼示例

//1.繼承Thread類

classMyThreadextendsThread{

publicMyThread() {

?? }

?

//2.重run方法

@Override

publicvoidrun() {

for(inti=0;i<100;i++) {

if(i%2==0) {

System.out.println(i);

? ? ? ? ?? }

? ? ?? }

?? }

}

?

publicclassThreadTest{

publicstaticvoidmain(String[]args) {

//3.新建Thread對象

MyThreadmyThread=newMyThread();

//4.調(diào)用start方法

myThread.start();

?? }

}

3.2. 方式二實現(xiàn)Runnable接口的方式:

創(chuàng)建一個實現(xiàn)了Runnable接口的類

實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()

創(chuàng)建實現(xiàn)類的對象

將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象

通過Thread類的對象調(diào)用start()

代碼示例:

//1. 創(chuàng)建一個實現(xiàn)了Runnable接口的類

publicclassRunnableTestimplementsRunnable{

// 2. 實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()

@Override

publicvoidrun() {

for(inti=0;i<100;i++) {

System.out.println(i);

? ? ?? }

?? }

}

?

classtest{

publicstaticvoidmain(String[]args) {

//3. 創(chuàng)建實現(xiàn)類的對象

RunnableTestrunnableTest=newRunnableTest();

//4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象

Threadthread=newThread(runnableTest);

//5. 通過Thread類的對象調(diào)用start()

thread.start();

?

?? }

}

兩種方式的對比:

開發(fā)中優(yōu)先選擇:實現(xiàn)Runnable接口的方式

原因:

1. 實現(xiàn)的方式?jīng)]類的單繼承性的局限性

2. 實現(xiàn)的方式更適合來處理多個線程共享數(shù)據(jù)的情況。

相同點:兩種方式都需要重寫run(),將線程要執(zhí)行的邏輯聲明在run()中。 目前兩種方式,要想啟動線程,都是調(diào)用的Thread類中的start()。

也可以采用創(chuàng)建匿名類的方式

publicclassThreadDemo{

publicstaticvoidmain(String[]args) {

?

//創(chuàng)建Thread類的匿名子類的方式

newThread() {

@Override

publicvoidrun() {

for(inti=0;i<100;i++) {

if(i%2==0) {

System.out.println(Thread.currentThread().getName()+":"+i);

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

? ? ? ? ?? }

}.start();

?

newThread() {

@Override

publicvoidrun() {

for(inti=0;i<100;i++) {

if(i%2!=0) {

System.out.println(Thread.currentThread().getName()+":"+i);

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

? ? ? ? ?? }

}.start();

?

?

?? }

}

4. Thread類的常用方法

4.1 常用方法:

start():啟動當前線程;調(diào)用當前線程的run(),只有Thread類和他的子類才能調(diào)用start()方法

run(): 通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中

currentThread():靜態(tài)方法,返回執(zhí)行當前代碼的線程

getName():獲取當前線程的名字

setName():設(shè)置當前線程的名字

yield():釋放當前cpu的執(zhí)行權(quán)

join():在線程a中調(diào)用線程b的join(),此時線程a就進入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)。

stop():已過時。當執(zhí)行此方法時,強制結(jié)束當前線程。

sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內(nèi),當前線程是阻塞狀態(tài)。

isAlive():判斷當前線程是否存活

4.2 線程的優(yōu)先級:

MAX_PRIORITY:10

MIN _PRIORITY:1

NORM_PRIORITY:5 -->默認優(yōu)先級

獲取和設(shè)置當前線程的優(yōu)先級:

getPriority():獲取線程的優(yōu)先級

setPriority(int p):設(shè)置線程的優(yōu)先級

說明:高優(yōu)先級的線程要搶占低優(yōu)先級線程CPU的執(zhí)行權(quán)。但是只是從概率上講,高優(yōu)先級的線程高概率的情況下被執(zhí)行。并不意味著只當高優(yōu)先級的線程執(zhí)行完以后,低優(yōu)先級的線程才執(zhí)行。

線程通信:wait() / notify() / notifyAll() :此三個方法定義在Object類中的。

線程的分類

守護線程,如:垃圾回收線程,依賴于主線程而存在

用戶線程,如:main方法的線程

5. Thread的生命周期

線程的五種狀態(tài):

新建:當一個 Thread類或其子類的對象被聲明并創(chuàng)建時,新生的線程對象處于新建狀態(tài)

就緒:處于新建狀態(tài)的線程被star()后,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件,只是沒分配到CPU資源

運行:當就緒的線程被調(diào)度并獲得CPU資源時,便進入運行狀態(tài),run()方法定義了線程的操作和功能

阻塞:在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時,讓出CP∪并臨時中止自己的執(zhí)行,進入阻塞狀態(tài)

死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現(xiàn)異常導致結(jié)束

說明:

生命周期關(guān)注兩個概念:狀態(tài)、相應(yīng)的方法

關(guān)注:狀態(tài)a-->狀態(tài)b:哪些方法執(zhí)行了(回調(diào)方法) 某個方法主動調(diào)用:狀態(tài)a-->狀態(tài)b

阻塞:臨時狀態(tài),不可以作為最終狀態(tài)

死亡:最終狀態(tài)。

四、線程的同步機制

1.背景

例子:創(chuàng)建個窗口賣票,總票數(shù)為100張.使用實現(xiàn)Runnable接口的方式

問題:賣票過程中,出現(xiàn)了重票、錯票 -->出現(xiàn)了線程的安全問題

問題出現(xiàn)的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票。

如何解決:當一個線程a在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他線程才可以開始操作ticket。這種情況即使線程a出現(xiàn)了阻塞,也不能被改變。

2. Java解決方案:同步機制

在Java中,我們通過同步機制,來解決線程的安全問題。

2.1 方式一:同步代碼塊

synchronized(同步監(jiān)視器){//同步監(jiān)視器就是需要同步線程的公共對象

//需要被同步的代碼


}

說明:

操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。

共享數(shù)據(jù):多個線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)。

同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。

要求多個線程必須要共用同一把鎖。

在實現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用this充當同步監(jiān)視器。

在繼承Thread類創(chuàng)建多線程的方式中,慎用this充當同步監(jiān)視器,考慮使用當前類充當同步監(jiān)視器。

代碼示例:

繼承Runnable接口形式同步代碼塊

publicclassTicketimplementsRunnable{

privateinttick=100;

?

@Override

publicvoidrun() {

?

while(true) {

synchronized(this) {

if(tick>0) {

System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為:"+tick--);

}else{

break;

? ? ? ? ? ? ?? }

? ? ? ? ?? }

? ? ?? }

?? }

}

?

classTicketTest{

publicstaticvoidmain(String[]args) {

Ticketticket=newTicket();

?

Threadthread1=newThread(ticket);

Threadthread2=newThread(ticket);

Threadthread3=newThread(ticket);

?

thread1.setName("窗口1");

thread2.setName("窗口2");

thread3.setName("窗口3");

?

thread1.start();

thread2.start();

thread3.start();

?

?? }

}

繼承Thread類形式同步代碼塊

publicclassTicket2extendsThread{

privatestaticinttick=100;

privatestaticObjectobject=newObject();

?

publicTicket2() {

?? }

?

@Override

publicvoidrun() {

?

while(true) {

synchronized(object) {

//synchronized (Ticket2.class) {//通過反射調(diào)用當前類

if(tick>0) {

System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為"+tick--);

}else{

break;

? ? ? ? ? ? ?? }

? ? ? ? ?? }

?

? ? ?? }

?? }

}

?

classTicketTest2{

publicstaticvoidmain(String[]args) {

Ticket2ticket1=newTicket2();

Ticket2ticket2=newTicket2();

Ticket2ticket3=newTicket2();

?

ticket1.setName("窗口1");

ticket2.setName("窗口2");

ticket3.setName("窗口3");

?

ticket1.start();

ticket2.start();

ticket3.start();

?

?

?? }

}

2.2 方式二:同步方法

如果操作共享數(shù)據(jù)的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。

publicsynchronizedvoidshow(Stringnamer){

....

}

代碼示例:

publicclassTicket3implementsRunnable{

privateinttick=100;

privatebooleanisFlag=true;

?

@Override

publicvoidrun() {

while(isFlag) {

show();

?

? ? ?? }

?? }

?

publicsynchronizedvoidshow() {//同步show方法,繼承Thread類方法一樣,只需同步方法即可,同時需要給方法加static關(guān)鍵字,確保不會創(chuàng)建多個對象

if(tick>0) {

try{

Thread.sleep(100);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ?? }

System.out.println(Thread.currentThread().getName()+"號窗口買票,票號為:"+tick--);

}else{

isFlag=false;

? ? ?? }

?? }

}

?

classTicketTest3{

publicstaticvoidmain(String[]args) {

Ticket3ticket=newTicket3();

?

Threadthread1=newThread(ticket);

Threadthread2=newThread(ticket);

Threadthread3=newThread(ticket);

?

thread1.setName("窗口1");

thread2.setName("窗口2");

thread3.setName("窗口3");

?

thread1.start();

thread2.start();

thread3.start();

?

?? }

}

2.3 方式三:Lock鎖 --- JDK 5.0新增

從JDK 5.0開始,Java提供了更強大的線程同步機制--通過顯式定義同步鎖對象來實現(xiàn)同步。同步鎖使用Lock對象充當。

java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應(yīng)先獲得Lock對象。

ReentrantLock類實現(xiàn)了Lock,它擁有與 synchronized相同的并發(fā)性和內(nèi)存語義,在實現(xiàn)線程安全的控制中,比較常用的是 Reentrantlock,可以顯式加鎖、釋放鎖。

classA{

//1.實例化ReentrantLock對象

privatefinalReenTrantLocklock=newReenTrantLook();

publicvoidm(){

lock.lock//2.先加鎖

try{

//保證線程同步的代碼

}finally{

lock.unlock();//3.后解鎖

? ? ?? }

?? }

}

?

//注意:如果同步代碼塊有異常,要將unlock()寫入finally語句塊中

代碼示例:

classWindowimplementsRunnable{

?

privateintticket=100;

//1.實例化ReentrantLock

privateReentrantLocklock=newReentrantLock();

?

@Override

publicvoidrun() {

while(true){

try{

?

//2.調(diào)用鎖定方法lock()

lock.lock();

?

if(ticket>0){

?

try{

Thread.sleep(100);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

?

System.out.println(Thread.currentThread().getName()+":售票,票號為:"+ticket);

ticket--;

}else{

break;

? ? ? ? ? ? ?? }

}finally{

//3.調(diào)用解鎖方法:unlock()

lock.unlock();

? ? ? ? ?? }

?

? ? ?? }

?? }

}

?

publicclassLockTest{

publicstaticvoidmain(String[]args) {

Windoww=newWindow();

?

Threadt1=newThread(w);

Threadt2=newThread(w);

Threadt3=newThread(w);

?

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

?

t1.start();

t2.start();

t3.start();

?? }

}

3.同步方法的總結(jié):

在《 Thinking in Java》中,是這么說的:對于并發(fā)工作,你需要某種方式來防止兩個任務(wù)訪問相同的資源(其實就是共享資源競爭)。防止這種沖突的方法就是當資源被一個任務(wù)使用時,在其上加鎖。第一個訪問某項資源的任務(wù)必須鎖定這項資源,使其他仼務(wù)在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務(wù)就可以鎖定并使用它了。

synchronized的鎖是什么

任意對象都可以作為同步鎖。所有對象都自動含有單一的鎖(監(jiān)視器)

同步方法的鎖:靜態(tài)方法(類名.class)、非靜態(tài)方法(this)

同步代碼塊:自己指定,很多時候也是指定為this或類名.class

注意點:

必須確保使用同一個資源的多個線程共用一把鎖,這個非常重要,否則就無法保證共享資源的安全

一個線程類中的所有靜態(tài)方法共用同一把鎖(類名.class),所有非靜態(tài)方法共用同一把鎖(this),同步代碼塊(指定需謹慎)

同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。

非靜態(tài)的同步方法,同步監(jiān)視器是:this

靜態(tài)的同步方法,同步監(jiān)視器是:當前類本身

4. 同步的范圍:

如何找問題,即代碼是否存在線程安全?(非常重要

(1)明確哪些代碼是多線程運行的代碼

(2)明確多個線程是否有共享數(shù)據(jù)

(3)明確多線程運行代碼中是否有多條語句操作共享數(shù)據(jù)

如何解決呢?(非常重要)

對多條操作共享數(shù)據(jù)的語句,只能讓一個線程都執(zhí)行完,在執(zhí)行過程中,其他線程不可以參與執(zhí)行。 即所有操作共享數(shù)據(jù)的這些語句都要放在同步范圍中

注意點:

范圍太?。簺]鎖住所有有安全問題的代碼 范圍太大:沒發(fā)揮多線程的功能。

5. 面試題:

1. synchronized 與 Lock的異同?

相同:二者都可以解決線程安全問題

不同:synchronized機制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器

Lock需要手動的啟動同步(lock(),同時結(jié)束同步也需要手動的實現(xiàn)(unlock())

使用的優(yōu)先順序:

Lock---> 同步代碼塊(已經(jīng)進入了方法體,分配了相應(yīng)資源 ) --->同步方法(在方法體之外)

利弊: 同步的方式,解決了線程的安全問題。---好處 操作同步代碼時,只能一個線程參與,其他線程等待。相當于是一個單線程的過程,效率低。

2. Java是如何解決線程安全問題的,有幾種方式?并對比幾種方式的不同

利用同步鎖的方式,有三種方式同步代碼塊、同步方法和用lock方法

3. synchronized和Lock方式解決線程安全問題的對比

相同:二者都可以解決線程安全問題

不同:synchronized機制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器

Lock需要手動的啟動同步(lock(),同時結(jié)束同步也需要手動的實現(xiàn)(unlock())

6. 線程安全的單例模式

使用同步機制將單例模式中的懶漢式改寫為線程安全的。

?

?

classBank{

?

privateBank(){}

?

privatestaticBankinstance=null;

?

publicstaticBankgetInstance(){

if(instance==null){

synchronized(Bank.class) {

if(instance==null){

?

instance=newBank();

? ? ? ? ? ? ?? }

? ? ? ? ?? }

? ? ?? }

returninstance;

?? }

?

}

6. 死鎖問題

死鎖的理解: 不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖

說明:

出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所的線程都處于阻塞狀態(tài),無法繼續(xù)

我們使用同步時,要避免出現(xiàn)死鎖。

死鎖舉例:

publicstaticvoidmain(String[]args) {

?

StringBuffers1=newStringBuffer();

StringBuffers2=newStringBuffer();

newThread(){

@Override

publicvoidrun() {

synchronized(s1){

s1.append("a");

s2.append("1");

try{

Thread.sleep(100);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ?? }

synchronized(s2){

s1.append("b");

s2.append("2");

System.out.println(s1);

System.out.println(s2);

? ? ? ? ? ? ?? }

?

?

? ? ? ? ?? }

?

? ? ?? }

}.start();

newThread(newRunnable() {

@Override

publicvoidrun() {

synchronized(s2){

s1.append("c");

s2.append("3");

try{

Thread.sleep(100);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ?? }

synchronized(s1){

s1.append("d");

s2.append("4");

?

System.out.println(s1);

System.out.println(s2);

? ? ? ? ? ? ?? }

? ? ? ? ?? }

? ? ?? }

}).start();

?

?

}

五、線程通訊

為了解決線程的死鎖問題,引入線程通訊

1. 線程通信涉及到的三個方法:

wait():一旦執(zhí)行此方法,當前線程就進入阻塞狀態(tài),并釋放同步監(jiān)視器。

notify():一旦執(zhí)行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優(yōu)先級高的那個。

notifyAll():一旦執(zhí)行此方法,就會喚醒所有被wait的線程。

2. 說明:

wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。

wait(),notify(),notifyAll()三個方法的調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器。

否則,會出現(xiàn)IllegalMonitorStateException異常

wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。

代碼示例:

使用兩個線程打印 1-100,線程1, 線程2 交替打印。

classMyThreadimplementsRunnable{

privateintnumber=1;

privateObjectobject=newObject();

?

@Override

publicvoidrun() {

while(true) {

?

synchronized(object) {

object.notify();//調(diào)用notify()方法喚醒線程

if(number<=100) {

//線程休眠

try{

Thread.sleep(10);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

?

System.out.println(Thread.currentThread().getName()+number);

number++;

?

try{

object.wait();//打印輸出一次后調(diào)用wait()方法將線程阻塞

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

}else{

break;

? ? ? ? ? ? ?? }

? ? ? ? ?? }

? ? ?? }

?? }

}

?

publicclassCommunicationTest{

publicstaticvoidmain(String[]args) {

MyThreadmyThread=newMyThread();

?

Threadthread1=newThread(myThread);

Threadthread2=newThread(myThread);

?

thread1.setName("線程1:");

thread2.setName("線程2:");

?

thread1.start();

thread2.start();

?? }

}

3. 面試題:

sleep() 和 wait()的異同?

相同點:一旦執(zhí)行方法,都可以使得當前的線程進入阻塞狀態(tài)。

不同點:

1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()

2)調(diào)用的要求不同:sleep()可以在任何需要的場景下調(diào)用。 wait()必須使用在同步代碼塊或同步方法中

3)關(guān)于是否釋放同步監(jiān)視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。

4. 釋放鎖的操作:

當前線程的同步方法、同步代碼塊執(zhí)行結(jié)束

當前線程在同步代碼塊、同步方法中遇到 break、 return終止了該代碼塊該方法的繼續(xù)執(zhí)行。

當前線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或 Exception,導致異常結(jié)束。

當前線程在同步代碼塊、同步方法中執(zhí)行了線程對象的 wait()方法,當前線程暫停,并釋放鎖

5. 不會釋放鎖的操作:

線程執(zhí)行同步代碼塊或同步方法時,程序調(diào)用 Thread. sleep()、Thread yield()方法暫停當前線程的執(zhí)行

線程執(zhí)行同步代碼塊時,其他線程調(diào)用了該線程的 suspend()方法將該線程掛起,該線程不會釋放鎖(同步監(jiān)視器)

應(yīng)盡量避免使用 suspend()和 resume()來控制線程

六、JDK 5.0新增線程創(chuàng)建方式

1. 新增方式一:實現(xiàn)Callable接口。

實現(xiàn)方法:

創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類

實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中

創(chuàng)建Callable接口實現(xiàn)類的對象

將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象

將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start()

獲取Callable中call方法的返回值

代碼示例:

//1.創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類

classNumThreadimplementsCallable{

//2.實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中

@Override

publicObjectcall()throwsException{

intsum=0;

for(inti=1;i<=100;i++) {

if(i%2==0){

System.out.println(i);

sum+=i;

? ? ? ? ?? }

? ? ?? }

returnsum;

?? }

}

?

?

publicclassThreadNew{

publicstaticvoidmain(String[]args) {

//3.創(chuàng)建Callable接口實現(xiàn)類的對象

NumThreadnumThread=newNumThread();

//4.將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象

FutureTaskfutureTask=newFutureTask(numThread);

//5.將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start()

newThread(futureTask).start();

?

try{

//6.獲取Callable中call方法的返回值

//get()返回值即為FutureTask構(gòu)造器參數(shù)Callable實現(xiàn)類重寫的call()的返回值。

Objectsum=futureTask.get();

System.out.println("總和為:"+sum);

}catch(InterruptedExceptione) {

e.printStackTrace();

}catch(ExecutionExceptione) {

e.printStackTrace();

? ? ?? }

?? }

?

}

如何理解實現(xiàn)Callable接口的方式創(chuàng)建多線程比實現(xiàn)Runnable接口創(chuàng)建多線程方式強大?

call()可以返回值的。

call()可以拋出異常,被外面的操作捕獲,獲取異常的信息

Callable是支持泛型的

2. 新增方式二:使用線程池

背景:經(jīng)常創(chuàng)建和銷毀、使用量特別大的資源,比如并發(fā)情況下的線程對性能影響很大。

解決方案:

提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中??梢员苊忸l繁創(chuàng)建銷毀、實現(xiàn)重復利用。類似生活中的公共交通工具。

實現(xiàn)方法:

提供指定線程數(shù)量的線程池

執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象

關(guān)閉連接池

相關(guān)API:

JDK5.0起提供了線程池相關(guān)AP|:ExecutorService和Executors

?

ExecutorService:真正的線程池接口。常見子類ThreadPoolexecutor

voidexecute(Runnablecommand):執(zhí)行任務(wù)/命令,沒有返回值,一般用來執(zhí)行Runnable

<T>Future<T>submit(Callable<T>task):執(zhí)行任務(wù),有返回值,一般又來執(zhí)行Callable

voidshutdown():關(guān)閉連接池

?

Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池

Executors.newCachedThreadPool():創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池

Executors.newFⅸedthreadPool(n);創(chuàng)建一個可重用固定線程數(shù)的線程池

EXecutors.newSingleThreadEXecutor():創(chuàng)建一個只有一個線程的線程池

Executors.newthreadPoo(n):創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。

代碼示例:

classNumberThreadimplementsRunnable{

?

@Override

publicvoidrun() {

for(inti=0;i<=100;i++){

if(i%2==0){

System.out.println(Thread.currentThread().getName()+": "+i);

? ? ? ? ?? }

? ? ?? }

?? }

}

?

classNumberThread1implementsRunnable{

?

@Override

publicvoidrun() {

for(inti=0;i<=100;i++){

if(i%2!=0){

System.out.println(Thread.currentThread().getName()+": "+i);

? ? ? ? ?? }

? ? ?? }

?? }

}

?

publicclassThreadPool{

?

publicstaticvoidmain(String[]args) {

//1. 提供指定線程數(shù)量的線程池

ExecutorServiceservice=Executors.newFixedThreadPool(10);

ThreadPoolExecutorservice1=(ThreadPoolExecutor)service;

//設(shè)置線程池的屬性

// ? ? ?? System.out.println(service.getClass());

// ? ? ?? service1.setCorePoolSize(15);

// ? ? ?? service1.setKeepAliveTime();

?

?

//2.執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象

service.execute(newNumberThread());//適合適用于Runnable

service.execute(newNumberThread1());//適合適用于Runnable

?

// ? ? ?? service.submit(Callable callable);//適合使用于Callable

//3.關(guān)閉連接池

service.shutdown();

?? }

?

}

應(yīng)用線程池的好處:

提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)

降低資源消耗(重復利用線程池中線程,不需要每次都創(chuàng)建)

便于線程管理

corePoolSize:核心池的大小

maximumPoolSize:最大線程數(shù)

keepAliveTime:線程沒任務(wù)時最多保持多長時間后會終止

面試題:Java中多線程的創(chuàng)建有幾種方式?四種。

即繼承Thread類重run方法

實現(xiàn)Runnable接口實現(xiàn)run方法

實現(xiàn)callable接口,實現(xiàn)call方法

利用線程池

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容