生活随笔
收集整理的這篇文章主要介紹了
5.Java SE 多线程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Java SE 多線程 創建多線程的四種方式 繼承 Thread 類 實現 Runnable 接口 實現 Callable 接口 (JDK5.0 新增) 線程池 線程的 常用方法 和 生命周期 鎖(synchronized、Lock:JDK5.0) synchronized Lock 接口:JDK5.0新增 死鎖 死鎖產生的原因 死鎖產生的四個必要條件? 如何防止死鎖? 線程的通信 線程通信應用 相關面試題 sleep() 和 wait()的異同? synchronized 與 Lock的異同?
創建多線程的四種方式
繼承 Thread 類
步驟: ① 用一個類繼承 Thread 類,重寫 run() 方法 ;run 方法中就是此線程需要執行的操作。 ② 在需要用到的地方 new 一個 繼承了 Thread 類的子類的對象 ,調用 start() 方法啟動線程。start() 會調用 run() 方法中的代碼。
public class Test extends Thread { @Override public void run ( ) { ……
}
}
class Main { public static void main ( String [ ] args
) { Test test
= new Test ( ) ; test
. start ( ) ; }
}
實現 Runnable 接口
相比 Thread 的優勢: ① 因為是接口,所以彌補了單繼承的局限性。 ② 節省資源,因為 Thread 每開啟一個線程就需要new Test() ,也就是資源類。Runnable 可以只用new 一個 資源類。
步驟: ① 用一個類實現 Runnable 接口,重寫 run 方法。 ② 在需要的地方創建上述類的子類,把創建的類放入 new Thread(子類)中,調用 start 方法。
public class Test implements Runnable { @Override public void run ( ) { ……
}
}
class Main { public static void main ( String [ ] args
) { Test test
= new Test ( ) ; new Thread ( test
) . start ( ) ; }
}
實現 Callable 接口 (JDK5.0 新增)
誕生原因(優勢):Callable 接口的方式比 Rannable 接口的方式更強大:因為 call() 可以有返回值、可以拋出異常,Callable 還支持泛型。 步驟:和 Runnable 差不多,只是 void -> Object(可變)、run -> call 、有 return Object、需要將資源類 放入 FutrueTask() 中,再放入 Thread 中。
public class Test implements Callable { @Override public Object call ( ) thorws
Exception { ……
return Object ; }
}
class Main { public static void main ( String [ ] args
) { Test test
= new Test ( ) ; FutureTask futureTask
= new FutureTask ( test
) ; new Thread ( futureTask
) . start ( ) ; try { Object obj
= futureTast
. get ( ) ; } catch ( ExecutionException e
) { e
. printStackTrace ( ) ; }
線程池
優勢: ① 提高了響應速度(減少了創建新線程的時間)。 ② 降低資源消耗 (重復利用線程池中線程,不需要每次都創建) ③ 便于線程管理,里面設有各種參數,如最大線程數、核心池大小、線程沒有任務時會保存多久終止等。暫時了解到這里,有興趣的看一下源碼就明白了。
class Test implements Runnable { @Override public void run ( ) { ……
}
}
class Main { public static void main ( String [ ] args
) { ExecutorService servie
= Executors . newFixedThreadPool ( 10 ) ; ThreadPoolExecutor service1
= ( ThreadPoolExecutor ) servie
; service
. execute ( new Test ( ) ) ; service
. submit ( Callable callable
) ; service
. shutdown ( ) ; }
}
線程的 常用方法 和 生命周期
常用方法:
測試
Thread 中的常用方法:
1. start ( ) : 啟動當前線程;調用當前線程的
run ( )
2. run ( ) : 通常需要重寫
Thread 類中的此方法,將創建的線程要執行的操作聲明在此方法中
3. currentThread ( ) : 靜態方法,返回執行當前代碼的線程
4. getName ( ) : 獲取當前線程的名字
5. setName ( ) : 設置當前線程的名字
6. yield ( ) : 釋放當前cpu的執行權,執行機會讓給相同或者更高優先級的線程。
7. join ( ) : 在線程a中調用線程b的
join ( ) , 此時線程a就進入阻塞狀態,直到線程b完全執行完以后,線程a才結束阻塞狀態。
8. stop ( ) : 已過時。當執行此方法時,強制結束當前線程。
9. sleep ( long millitime
) : 讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態,不釋放鎖。
10. isAlive ( ) : 判斷當前線程是否存活線程的優先級:
1.
MAX_PRIORITY:
10
MIN _PRIORITY:
1
NORM_PRIORITY:
5 -- > 默認優先級
2. 如何獲取和設置當前線程的優先級:
getPriority ( ) : 獲取線程的優先級
setPriority ( int p
) : 設置線程的優先級說明:高優先級的線程要搶占低優先級線程cpu的執行權。但是只是從概率上講,高優先級的線程高概率的情況下被執行。并不意味著只有當高優先級的線程執行完以后,低優先級的線程才執行。
生命周期
① 就緒狀態(Runnable):該狀態的線程位于可運行的線程池中,等待獲取 CPU 的使用權。 ② 運行狀態(Running):就緒狀態獲取了 CPU ,執行代碼。 ③ 阻塞狀態(Blocked):因為某種原因放棄 CPU 的使用權,暫時停止運行。阻塞狀態分三種: (一)等待阻塞(wait):運行的線程執行 wait 方法,JVM 會把該線程放入等待池中。(wait 會釋放持有的鎖 )。 (二)同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用了,則JVM 會把該線程放入鎖池中。 (三)其他阻塞:運行的線程執行 sleep、join 方法或者發出了 I/O 請求時,JVM 會把該線程設置為阻塞狀態。當 sleep 狀態超時、join 等待線程終止或者超時、或者 I/O 處理完畢時,線程重新轉入就緒狀態。(注意:sleep 是不會釋放持有鎖 )
鎖(synchronized、Lock:JDK5.0)
synchronized
介紹:synchronized 是 Java 的關鍵字,用來給對象 、方法 、代碼塊 、類 加鎖;當他鎖定一個方法或者一個代碼塊時,同一時刻最多只能有一個線程執行這段代碼。它是解決線程安全問題的方式之一。 看個例子:說買票過程中,出現了重票、錯票,也就是線程安全問題,那怎么解決呢? ① 分析原因:兩個線程操作了同一張票,導致了重票。 ② 解決方法:也就是說,每賣一張票,只能有一個線程去操作,其他的線程必須等待。代碼如下:
class Window implements Runnable { private int ticket
= 100 ; @Override public void run ( ) { while ( true ) { synchronized ( this ) { if ( ticket
> 0 ) { System . out
. println ( Thread . currentThread ( ) . getName ( ) + ":賣票,票號為:" + ticket
) ; ticket
-- ; } else { break ; } } } }
}
public class Main { public static void main ( String [ ] args
) { Window w
= new Window ( ) ; Thread t1
= new Thread ( w
) ; Thread t2
= new Thread ( w
) ; t1
. setName ( "窗口1" ) ; t2
. setName ( "窗口2" ) ; t1
. start ( ) ; t2
. start ( ) ; }
③ 上述代碼的 synchronized 是放在代碼塊的,此時稱為 同步代碼塊 ,我們也可以把它放在方法上,run 去調用這個方法就行了,此時稱為 同步方法 (多用這個 ) 其實差不多,看下代碼:
class Window implements Runnable {
public void run ( ) { while ( true ) { show ( ) ; } }
private synchronized void show ( ) { if ( ticket
> 0 ) ……
}
}
Lock 接口:JDK5.0新增
介紹:Lock 鎖比 synchronized 要靈活一些;前者需要手動啟動 (lock()),同步結束也需要手動釋放鎖(unlock());后者在執行完相應同步代碼后,自動釋放同步監視器(鎖)。因此 Lock 多用在同步代碼塊 ,此時已經進入了方法體,分配了相應資源。 步驟:① 先 new 一個實現了 Lock 接口的類的 對象,也就是 ReentrantLock;② 就是上鎖,位置和 synchronized 差不多;只需要最后得 釋放鎖,因此一般用 lock 都在 try- finally 里面。
class Window implements Runnable { private int ticket
= 100 ; private ReentrantLock lock
= new ReentrantLock ( ) ; @Override public void run ( ) { while ( true ) { try { lock
. lock ( ) ; if ( ticket
> 0 ) { System . out
. println ( Thread . currentThread ( ) . getName ( ) + ":售票,票號為:" + ticket
) ; ticket
-- ; } else { break ; } } finally { lock
. unlock ( ) ; } } }
public class Main { public static void main ( String [ ] args
) { Window w
= new Window ( ) ; Thread t1
= new Thread ( w
) ; Thread t2
= new Thread ( w
) ; t1
. setName ( "窗口1" ) ; t2
. setName ( "窗口2" ) ; t1
. start ( ) ; t2
. start ( ) ; } }
死鎖
介紹:是指多個線程在運行過程中,因爭奪資源而造成的一種僵局,當處于這種僵持狀態時,若無外力作用,他們都無法再向前推進。最簡單的就是:不同的線程占用對方需要的同步資源不放棄,都在等著對方放棄自己需要的同步資源,就形成了死鎖;而且不會有異常,不會提示,只是所有線程都處于阻塞狀態,無法繼續。通俗來講就是,你綁了他老婆,他綁了你老婆,但是你和他都在等待對方放了自己的老婆,僵持。 先看個例子:
public class DeadLock { public static void main ( String [ ] args
) { StringBuffer s1
= new StringBuffer ( ) ; StringBuffer s2
= new StringBuffer ( ) ; new Thread ( ) { @Override public void run ( ) { synchronized ( s1
) { s1
. append ( "a" ) ; s2
. append ( "1" ) ; synchronized ( s2
) { s1
. append ( "b" ) ; s2
. append ( "2" ) ; System . out
. println ( s1
) ; System . out
. println ( s2
) ; } } } } . start ( ) ; new Thread ( ) { @Override public void run ( ) { synchronized ( s2
) { s1
. append ( "c" ) ; s2
. append ( "3" ) ; synchronized ( s1
) { s1
. append ( "d" ) ; s2
. append ( "4" ) ; System . out
. println ( s1
) ; System . out
. println ( s2
) ; } } } } . start ( ) ; } }
死鎖產生的原因
前置知識:系統中資源可以分為兩類:可剝奪資源 和 不可剝奪資源 ① 可剝奪資源:是指在某線程獲得這類資源后,該資源還可以被其他線程或者系統剝奪,CPU 和 主存均屬于可剝奪性資源。 ② 不可剝奪資源:當系統把這類資源分配給某進程后,再不能強行收回,只能在進程用完后釋放;如打印機。 產生死鎖的原因如下:
競爭資源: ① 競爭不可剝奪資源;如系統中只有一臺打印機R1和一臺磁帶機R2,可供進程P1和P2共享。假定P1已占用了打印機R1,P2已占用了磁帶機R2,若P2繼續要求打印機R1,P2將阻塞;P1若又要求磁帶機R2,P1也將阻塞。于是,在P1和P2之間就形成了僵局,兩個進程都在等待對方釋放自己所需要的資源,但是它們又都因不能繼續獲得自己所需要的資源而不能繼續推進,從而也不能釋放自己所占有的資源,以致進入死鎖狀態。
② 競爭臨時資源:通常消息通信順序進行不當,則會產生死鎖。 2. 進程推進順序非法: 若P1保持了資源R1,P2保持了資源R2,系統處于不安全狀態,因為這兩個進程再向前推進,便可能發生死鎖。例如,當P1運行到P1:Request(R2)時,將因R2已被P2占用而阻塞;當P2運行到P2:Request(R1)時,也將因R1已被P1占用而阻塞,于是發生進程死鎖。
死鎖產生的四個必要條件?
互斥條件:線程要求對所分配的資源進行排它性控制,即在一段時間內某一資源僅為一個線程鎖占用。 請求并保持條件:當線程因請求資源而阻塞,對已獲得的資源保持不放。 不剝奪條件:進程已獲得的資源在未使用完之前,不能剝奪,只能在使用完時自己釋放。 環路等待條件:在發生死鎖時,必然存在一個進程—資源的環形鏈。環路上的每個進程都在等待下一個進程占有的資源。
如何防止死鎖?
① 破壞四個必要條件;② 預防死鎖(有相關算法,這里暫不討論)
破壞互斥條件:如果允許系統資源都能共享使用 ,則系統不會進入死鎖狀態。 破壞請求條件并保持條件:采用預先靜態預分配,即進程運行前一次性申請完它所需要的物資,在物資為滿足前,不能投入運行;一旦運行后,這些物資就一直歸他所有,也不再發出其他物資請求。 破壞不可剝奪條件:當一個保持了某些不可剝奪資源的進程,請求新的資源沒有辦法滿足時,它必須釋放已經保持的所有資源,待以后需要時在重新申請。 破壞環路等待條件:采用順序資源分配法。首先給系統中的資源編號,規定每個進程必須按編號遞增的順序請求資源,同類資源一次申請完。
線程的通信
線程的通信涉及到三個方法
wait ( ) :一旦執行此方法,當前線程就進入阻塞狀態,并釋放同步監視器(鎖)。
notify ( ) :一旦執行此方法,就會喚醒被 wait 的線程。如果有多個線程被 wait ,就換醒優先級最高的那個。
notifyAll ( ) :一旦執行此方法,就會喚醒所有被 wait 的線程。
說明:
這三個方法是定義在
java. lang. Object 類中;
必須使用在同步代碼塊或同步方法中;
它們的調用者必須是同步代碼塊或同步方法中的同步監視器。
看個線程通信的例子:使用兩個線程打印 1-100。線程一,線程二 交替打印。
public class Main { public static void main ( String [ ] args
) { Number number
= new Number ( ) ; Thread t1
= new Thread ( number
) ; Thread t2
= new Thread ( number
) ; t1
. setName ( "線程一" ) ; t2
. setName ( "線程二" ) ; t1
. start ( ) ; t2
. start ( ) ; } static class Number implements Runnable { private int num
= 1 ; private Object obj
= new Object ( ) ; @Override public void run ( ) { while ( true ) { synchronized ( obj
) { obj
. notify ( ) ; if ( num
<= 100 ) { System . out
. println ( Thread . currentThread ( ) . getName ( ) + ":" + num
++ ) ; try { obj
. wait ( ) ; } catch ( InterruptedException e
) { e
. printStackTrace ( ) ; } } else { break ; } } } } }
}
線程通信應用
必須掌握的經典例子:生產者 / 消費者 問題。 問題:生產者(Productor)將產品交給店員(clerk),而消費者(Customer)從店員哪里取走產品,店員一次只能持有固定數量的產品(如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了在通知消費者來取走產品。
class Clerk { private int productCount
= 0 ; public synchronized void produceProduct ( ) { if ( productCount
< 20 ) { productCount
++ ; System . out
. println ( Thread . currentThread ( ) . getName ( ) + ":開始生產第" + productCount
+ "個產品" ) ; notify ( ) ; } else { try { wait ( ) ; } catch ( InterruptedException e
) { e
. printStackTrace ( ) ; } } } public synchronized void consumeProduct ( ) { if ( productCount
> 0 ) { System . out
. println ( Thread . currentThread ( ) . getName ( ) + ":開始消費第" + productCount
+ "個產品" ) ; productCount
-- ; notify ( ) ; } else { try { wait ( ) ; } catch ( InterruptedException e
) { e
. printStackTrace ( ) ; } } }
} class Producer extends Thread { private Clerk clerk
; public Producer ( Clerk clerk
) { this . clerk
= clerk
; } @Override public void run ( ) { System . out
. println ( getName ( ) + ":開始生產產品....." ) ; while ( true ) { try { Thread . sleep ( 10 ) ; } catch ( InterruptedException e
) { e
. printStackTrace ( ) ; } clerk
. produceProduct ( ) ; } }
} class Consumer extends Thread { private Clerk clerk
; public Consumer ( Clerk clerk
) { this . clerk
= clerk
; } @Override public void run ( ) { System . out
. println ( getName ( ) + ":開始消費產品....." ) ; while ( true ) { try { Thread . sleep ( 20 ) ; } catch ( InterruptedException e
) { e
. printStackTrace ( ) ; } clerk
. consumeProduct ( ) ; } }
} public class ProductTest { public static void main ( String [ ] args
) { Clerk clerk
= new Clerk ( ) ; Producer p1
= new Producer ( clerk
) ; p1
. setName ( "生產者1" ) ; Consumer c1
= new Consumer ( clerk
) ; c1
. setName ( "消費者1" ) ; Consumer c2
= new Consumer ( clerk
) ; c2
. setName ( "消費者2" ) ; p1
. start ( ) ; c1
. start ( ) ; c2
. start ( ) ; }
}
相關面試題
sleep() 和 wait()的異同?
相同點:都可以使當前的進程進入阻塞狀態。 不同點: ① 聲明位置不同:Thread 類中聲明 sleep() ,Object 類中聲明 wait()。 ② 調用的要求不同:sleep() 可以在任何需要的場景調用。wait() 必須使用在同步代碼塊或同步方法中。 ③ 關于是否釋放同步監視器(鎖):如果兩個方法都使用在同步代碼塊或同步方法中,sleep() 不會釋放鎖,wait() 會釋放鎖。
synchronized 與 Lock的異同?
相同:二者都可以解決線程安全問題 不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器,Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
參考: https://www.bilibili.com/video/BV1Kb411W75N,https://blog.csdn.net/Amosstan/article/details/120161969,https://blog.csdn.net/hd12370/article/details/82814348
總結
以上是生活随笔 為你收集整理的5.Java SE 多线程 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。