【Java基础总结】多线程
?1. 實現多線程的兩種方式
1 //第一種:繼承Thread類,重寫run()方法 2 class ThreadTest1 extends Thread{ 3 public void run(){ 4 String threadName = Thread.currentThread().getName(); 5 for(int i=0;i<10;i++){ 6 System.out.println("ThreadTest1 "+threadName+" running ... "+i); 7 } 8 } 9 } 10 11 //第二種:實現Runnable接口,重寫run()方法 12 class ThreadTest2 implements Runnable{ 13 public void run(){ 14 String threadName = Thread.currentThread().getName(); 15 for(int i=0;i<10;i++){ 16 System.out.println("ThreadTest2 "+threadName+" running ... "+i); 17 } 18 } 19 }實現方式不同,使用方式也不同
public class Demo1{ public static void main(String[] args){ThreadTest1 t1 = new ThreadTest1();ThreadTest1 t2 = new ThreadTest1();Thread t3 = new Thread(new ThreadTest2());Thread t4 = new Thread(new ThreadTest2());t1.start();t2.start();t3.start();t4.start();} }運行結果大致如下:
ThreadTest1 Thread-0 running ... 0 ThreadTest2 Thread-3 running ... 0 ThreadTest2 Thread-2 running ... 0 ThreadTest1 Thread-1 running ... 0 ThreadTest2 Thread-2 running ... 1 ThreadTest2 Thread-3 running ... 1 ThreadTest1 Thread-0 running ... 1 ThreadTest2 Thread-3 running ... 2 ThreadTest2 Thread-2 running ... 2 ThreadTest1 Thread-1 running ... 1 ThreadTest2 Thread-2 running ... 3 ThreadTest2 Thread-3 running ... 3 ThreadTest1 Thread-0 running ... 2 ThreadTest2 Thread-3 running ... 4 ThreadTest2 Thread-2 running ... 4 ThreadTest1 Thread-1 running ... 2 ThreadTest2 Thread-2 running ... 5 ThreadTest2 Thread-3 running ... 5 ThreadTest1 Thread-0 running ... 3 ThreadTest2 Thread-3 running ... 6 ThreadTest2 Thread-2 running ... 6 ThreadTest1 Thread-1 running ... 3 ThreadTest2 Thread-2 running ... 7 ThreadTest2 Thread-3 running ... 7 ThreadTest2 Thread-3 running ... 8 ThreadTest2 Thread-3 running ... 9 ThreadTest1 Thread-0 running ... 4 ThreadTest1 Thread-0 running ... 5 ThreadTest2 Thread-2 running ... 8 ThreadTest2 Thread-2 running ... 9 ThreadTest1 Thread-1 running ... 4 ThreadTest1 Thread-0 running ... 6 ThreadTest1 Thread-0 running ... 7 ThreadTest1 Thread-0 running ... 8 ThreadTest1 Thread-1 running ... 5 ThreadTest1 Thread-0 running ... 9 ThreadTest1 Thread-1 running ... 6 ThreadTest1 Thread-1 running ... 7 ThreadTest1 Thread-1 running ... 8 ThreadTest1 Thread-1 running ... 9 View Code?
2. 線程共享資源
建議使用?實現Runnable接口,重寫run方法?的方式來實現多線程,它有如下優點:
1. 線程和代碼分離,多線程間可以共享資源
2. 避免了單繼承帶來的局限性
3.?多線程之間可以共享資源
tip
Thread.currentThread().getName() 獲得當前線程的名稱 threadObj.setName() 設置線程名稱?
案例:售票
class ThreadTest4 implements Runnable{private int ticket=20;public void run(){String threadName = Thread.currentThread().getName();while(ticket>0){System.out.println("ThreadTest4 "+threadName+" 售出 "+ticket+" 號票");ticket--;}} }使用情況1:
(new Thread(new ThreadTest4(), "窗口a")).start();(new Thread(new ThreadTest4(), "窗口b")).start();(new Thread(new ThreadTest4(), "窗口c")).start();(new Thread(new ThreadTest4(), "窗口d")).start();運行情況說明A、B、C、D四個窗口也沒用共享count這個資源
主線程名稱:main ThreadTest4 窗口a 售出 20 號票 ThreadTest4 窗口a 售出 19 號票 ThreadTest4 窗口b 售出 20 號票 ThreadTest4 窗口b 售出 19 號票 ThreadTest4 窗口b 售出 18 號票 ThreadTest4 窗口b 售出 17 號票 ThreadTest4 窗口b 售出 16 號票 ThreadTest4 窗口b 售出 15 號票 ThreadTest4 窗口a 售出 18 號票 ThreadTest4 窗口b 售出 14 號票 ThreadTest4 窗口d 售出 20 號票 ThreadTest4 窗口c 售出 20 號票 ThreadTest4 窗口d 售出 19 號票 ThreadTest4 窗口b 售出 13 號票 ThreadTest4 窗口a 售出 17 號票 ThreadTest4 窗口b 售出 12 號票 ThreadTest4 窗口d 售出 18 號票 ThreadTest4 窗口c 售出 19 號票 ThreadTest4 窗口d 售出 17 號票 ThreadTest4 窗口b 售出 11 號票 ThreadTest4 窗口a 售出 16 號票 ThreadTest4 窗口b 售出 10 號票 ThreadTest4 窗口d 售出 16 號票 ThreadTest4 窗口c 售出 18 號票 ThreadTest4 窗口d 售出 15 號票 ThreadTest4 窗口b 售出 9 號票 ThreadTest4 窗口a 售出 15 號票 ThreadTest4 窗口b 售出 8 號票 ThreadTest4 窗口d 售出 14 號票 ThreadTest4 窗口c 售出 17 號票 ThreadTest4 窗口d 售出 13 號票 ThreadTest4 窗口b 售出 7 號票 ThreadTest4 窗口a 售出 14 號票 ThreadTest4 窗口b 售出 6 號票 ThreadTest4 窗口d 售出 12 號票 ThreadTest4 窗口c 售出 16 號票 ThreadTest4 窗口d 售出 11 號票 ThreadTest4 窗口b 售出 5 號票 ThreadTest4 窗口a 售出 13 號票 ThreadTest4 窗口b 售出 4 號票 ThreadTest4 窗口d 售出 10 號票 ThreadTest4 窗口c 售出 15 號票 ThreadTest4 窗口d 售出 9 號票 ThreadTest4 窗口b 售出 3 號票 ThreadTest4 窗口a 售出 12 號票 ThreadTest4 窗口b 售出 2 號票 ThreadTest4 窗口d 售出 8 號票 ThreadTest4 窗口c 售出 14 號票 ThreadTest4 窗口d 售出 7 號票 ThreadTest4 窗口b 售出 1 號票 ThreadTest4 窗口a 售出 11 號票 ThreadTest4 窗口a 售出 10 號票 ThreadTest4 窗口d 售出 6 號票 ThreadTest4 窗口c 售出 13 號票 ThreadTest4 窗口d 售出 5 號票 ThreadTest4 窗口a 售出 9 號票 ThreadTest4 窗口d 售出 4 號票 ThreadTest4 窗口c 售出 12 號票 ThreadTest4 窗口d 售出 3 號票 ThreadTest4 窗口a 售出 8 號票 ThreadTest4 窗口d 售出 2 號票 ThreadTest4 窗口c 售出 11 號票 ThreadTest4 窗口d 售出 1 號票 ThreadTest4 窗口a 售出 7 號票 ThreadTest4 窗口c 售出 10 號票 ThreadTest4 窗口a 售出 6 號票 ThreadTest4 窗口a 售出 5 號票 ThreadTest4 窗口a 售出 4 號票 ThreadTest4 窗口a 售出 3 號票 ThreadTest4 窗口a 售出 2 號票 ThreadTest4 窗口a 售出 1 號票 ThreadTest4 窗口c 售出 9 號票 ThreadTest4 窗口c 售出 8 號票 ThreadTest4 窗口c 售出 7 號票 ThreadTest4 窗口c 售出 6 號票 ThreadTest4 窗口c 售出 5 號票 ThreadTest4 窗口c 售出 4 號票 ThreadTest4 窗口c 售出 3 號票 ThreadTest4 窗口c 售出 2 號票 ThreadTest4 窗口c 售出 1 號票 View Code使用情況2:
ThreadTest4 t2 = new ThreadTest4();(new Thread(t2,"窗口1")).start();(new Thread(t2,"窗口2")).start();(new Thread(t2,"窗口3")).start();(new Thread(t2,"窗口4")).start();運行情況說明A、B、C、D四個窗口共享count這個資源(但發生了訪問沖突)
主線程名稱:main ThreadTest4 窗口1 售出 20 號票 ThreadTest4 窗口2 售出 20 號票 ThreadTest4 窗口2 售出 19 號票 ThreadTest4 窗口2 售出 18 號票 ThreadTest4 窗口2 售出 17 號票 ThreadTest4 窗口2 售出 16 號票 ThreadTest4 窗口2 售出 15 號票 ThreadTest4 窗口2 售出 14 號票 ThreadTest4 窗口2 售出 13 號票 ThreadTest4 窗口2 售出 12 號票 ThreadTest4 窗口2 售出 11 號票 ThreadTest4 窗口2 售出 10 號票 ThreadTest4 窗口2 售出 9 號票 ThreadTest4 窗口2 售出 8 號票 ThreadTest4 窗口2 售出 7 號票 ThreadTest4 窗口3 售出 7 號票 ThreadTest4 窗口1 售出 5 號票 ThreadTest4 窗口2 售出 6 號票 ThreadTest4 窗口1 售出 3 號票 ThreadTest4 窗口4 售出 4 號票 ThreadTest4 窗口3 售出 4 號票 ThreadTest4 窗口1 售出 1 號票 ThreadTest4 窗口2 售出 2 號票 View Code?
3. 線程同步
? ?多線程中涉及到共享數據時,會出現線程安全問題。就上面的售票案例來說,若沒有加?synchronized?關鍵字,在多個線程同時使用ticket這個共享數據時,會出現同一個ticket被使用兩次這樣的看似不可能的情況。另外還有一種情況,事實上ticket這個共享數據是類ThreadTest4對象t2中的變量,所以若是在主線程中添加t2.run();語句的話,也是會發生線程安全問題的。
? ?在Java里面,同步鎖的概念就是這樣的。任何一個Object Reference都可以作為同步鎖。我們可以把Object Reference理解為對象在內存分配系統中的內存地址。
1 class ThreadTest5 implements Runnable{ 2 private int ticket=20; 3 public void run(){ 4 while(ticket>0){ 5 String threadName = Thread.currentThread().getName(); 6 //同步代碼塊(越小越好) 7 synchronized(this){ 8 if(ticket>0){ 9 System.out.println(threadName + " sales ticket "+ticket); 10 ticket--; 11 } 12 } 13 } 14 } 15 } 16 17 public class Demo3{ 18 public static void main(String[] args){ 19 ThreadTest5 t = new ThreadTest5(); 20 (new Thread(t, "窗口A")).start(); 21 (new Thread(t, "窗口B")).start(); 22 (new Thread(t, "窗口C")).start(); 23 (new Thread(t, "窗口D")).start(); 24 } 25 }運行結果
窗口A sales ticket 20 窗口A sales ticket 19 窗口A sales ticket 18 窗口A sales ticket 17 窗口A sales ticket 16 窗口A sales ticket 15 窗口A sales ticket 14 窗口A sales ticket 13 窗口A sales ticket 12 窗口A sales ticket 11 窗口A sales ticket 10 窗口D sales ticket 9 窗口D sales ticket 8 窗口D sales ticket 7 窗口D sales ticket 6 窗口D sales ticket 5 窗口D sales ticket 4 窗口D sales ticket 3 窗口C sales ticket 2 窗口B sales ticket 1 View Code(1)同步代碼塊
synchronized(類或對象){//需要同步的代碼段 }(2)同步函數
(非static的情況)
public synchronized void fun(){//代碼段 }等價于(調用此同步函數的對象作為此同步函數的同步鎖)
public void fun() {synchronized(this) {//代碼段 } }(static的情況)
public static synchronized void fun() {//代碼段 }靜態變量或靜態方法加載到內存中時,內存中沒有本類對象,但一定有了該類對應的字節碼文件(類名.class),該對象的類型是class。靜態同步函數使用的同步鎖是所在類的字節碼文件。
?
線程同步的前提:
1)2個或2個以上的線程
2)使用同一把鎖
保證同步中只有一個線程在運行。
好處:解決線程安全問題
弊端:多個線程需要判斷鎖,消耗資源
?
4. 線程通信
經典的生產者和消費者問題
假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中的產品取走消費。如果倉庫中沒有產品,則生產者可以將產品放入倉庫,否則停止生產并等待,直到倉庫中的產品被消費者取走為止。如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費并等待,直到倉庫中再次放入產品為止。顯然,這是一個同步問題,生產者和消費者共享同一資源,并且,生產者和消費者之間彼此依賴,互為條件向前推進
4.1 synchronized和wait、notify、notifyAll
?wait()?使得當前線程必須要等待,并釋放對鎖的擁有權,等到另外一個線程調用notify()或者notifyAll()方法
?notify()?會喚醒一個等待當前對象的鎖的線程
?notifyAll()?喚醒所有一個等待當前對象的鎖的線程
一個小比較
當線程調用了wait()方法時,它會釋放掉對象的鎖。
另一個會導致線程暫停的方法:Thread.sleep(millisecond),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放掉對象的鎖的。
其中一種運行結果:
1 [消費者1]倉庫沒有饅頭!! 2 [消費者2]倉庫沒有饅頭!! 3 [生產者A]倉庫增加了一個饅頭,現有饅頭[1] 個 4 [消費者2]倉庫減少了一個饅頭,現有饅頭[0] 個 5 [消費者1]倉庫沒有饅頭!! 6 [生產者B]倉庫增加了一個饅頭,現有饅頭[1] 個 7 [消費者1]倉庫減少了一個饅頭,現有饅頭[0] 個 8 [生產者A]倉庫增加了一個饅頭,現有饅頭[1] 個 9 [生產者B]倉庫增加了一個饅頭,現有饅頭[2] 個 10 [生產者A]倉庫增加了一個饅頭,現有饅頭[3] 個 11 [消費者2]倉庫減少了一個饅頭,現有饅頭[2] 個 12 [生產者B]倉庫增加了一個饅頭,現有饅頭[3] 個 13 [消費者1]倉庫減少了一個饅頭,現有饅頭[2] 個 14 [生產者A]倉庫增加了一個饅頭,現有饅頭[3] 個 15 [生產者B]倉庫增加了一個饅頭,現有饅頭[4] 個 16 [消費者2]倉庫減少了一個饅頭,現有饅頭[3] 個 17 [生產者A]倉庫增加了一個饅頭,現有饅頭[4] 個 18 [消費者1]倉庫減少了一個饅頭,現有饅頭[3] 個 19 [生產者B]倉庫增加了一個饅頭,現有饅頭[4] 個 20 [生產者A]倉庫增加了一個饅頭,現有饅頭[5] 個 21 [生產者B]倉庫已達到最大容量 5 個 !! 22 [消費者2]倉庫減少了一個饅頭,現有饅頭[4] 個 23 [生產者B]倉庫增加了一個饅頭,現有饅頭[5] 個 24 [生產者A]倉庫已達到最大容量 5 個 !! 25 [消費者1]倉庫減少了一個饅頭,現有饅頭[4] 個 26 [生產者A]倉庫增加了一個饅頭,現有饅頭[5] 個 27 [生產者B]倉庫已達到最大容量 5 個 !! 28 [生產者A]倉庫已達到最大容量 5 個 !!(運行結果分析)
1)線程“消費者1”調用repositoy.fetch()方法去倉庫取饅頭。進入synchronized塊,剛一執行while語句,結果 this.count<1 為真,直接就wait()進入等待隊列,最后釋放了鎖 (就緒隊列[消費者2,生產者A,生產者B],等待隊列[消費者1]) 2)線程“消費者2”同“消費者1”的遭遇是相同的(對此,我們深表同情) (就緒隊列[生產者A,生產者B], 等待隊列[消費者1,消費者2]) 3)線程“生產者A”生產完商品后,調用repository.store()方法把商品存到了倉庫中。在store方法里,使用notifyAll方法喚醒了所有在沉睡wait的線程,最后釋放了鎖 (就緒隊列[生產者A,生產者B,消費者1,消費者2],等待隊列[]) 4)線程“消費者2”從上次wait的地方開始執行(從哪里跌倒,就從哪里爬起來)。同樣是while語句,但這次 this.count<1 不為真了,于是乎順利脫坑,接著執行 this.count--,把倉庫僅有的一個饅頭給拿走了,走之前還不忘大喊一句“倉庫減少了一個饅頭,現有饅頭[0] 個”。同樣是notifyAll方法喚醒所有沉睡wait的線程,釋放鎖 (就緒隊列[生產者A,生產者B,消費者1,消費者2],等待隊列[]) 5)線程“消費者1”辛辛苦苦搶到了鎖,終于能執行syschronized塊代碼了。和“消費者2”一樣,也是從上次wait的地方接著執行,也是while語句,但不同的是 this.count<1 為真,線程“生產者A”生產存放到倉庫的僅有的一個饅頭被“消費者2”給吃了,可以想象此時的“消費者1”心里是萬念俱灰的。啥也別說了,接著沉睡wait吧。(釋放了鎖) (就緒隊列[生產者A,生產者B,消費者2],等待隊列[消費者1])**** 在接下來的幾十個回合中,時而生產者線程奪得倉庫鎖,稱霸武林,時而消費者線程奪得,笑傲江湖,倉庫商品也是時增時減,但總體上還是生產者線程搶到的次數多,因為生產者夠快,當消費者還在花2秒鐘費勁的消化時,生產者早就1秒生產完畢,參與倉庫鎖的再次爭奪了,可見“天下武功,唯快不破” *****“倉庫里沒有饅頭為什么不通知我?”,線程“消費者2”不滿“消費者1”對倉庫情況的隱瞞不報,“你要是早告訴我,我也就不用爭搶倉庫鎖,搶到了也沒用,里面根本就沒有饅頭,去了也是wait”。(第1、2步) “你還有臉說我,你把生產者A生產存放在倉庫僅有的一個饅頭吃了,你明知道倉庫再也沒有饅頭了,也不告訴我,還讓我傻了吧唧搶到倉庫鎖,去了也白搭”。“消費者1”反駁道,它同樣也很委屈。(第4、5步) “管我什么事,搶倉庫鎖的有不只你一個,我也去搶了,白費了勁,還沒搶到....”,線程“消費者2”道。“要是倉庫沒有饅頭的時侯,只喚醒那幫生產者就好了”,線程“消費者1”和“消費者2”異口同聲的說道。
處理線程通信必須遵循一種原則:對于生產者,在生產者沒有生產之前,要通知消費者等待;在生產者生產之后,馬上又通知消費者消費;對于消費者,在消費者消費之后,要通知生產者已經消費結束,需要繼續生產新的產品以供消費。
4.2 Lock和Condition
Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的 Condition 對象。
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用
?
5. 停止線程的方法
interrupt() //停止線程 isInterrupt() //判斷線程是否停止6. 守護線程和join方法
守護線程是為其他線程提供便利服務的,當全部的用戶線程結束后,守護線程才會隨JVM結束工作。?thread.setDaemon(true);?
public static void main(String[] args){ Thread t3 = new Thread(test2, "線程t3");t3.start();t3.join(); //主線程就此陷入等待,直到t3線程結束。 }7. 線程優先級和yield方法
線程的優先級從低到高:1-10,優先級高的的優先執行,每個新線程都繼承了父線程的優先級,常量:Thread.MIN_PRIORITY 值為1,Thread.MAX_PRIORITY 值為10,Thread.NORM_PRIORITY 值為5
void setPriority(priority) //設置線程優先級 int getPriority() //獲取線程優先級?yield()?線程從執行狀態變成就緒狀態。
?
轉載于:https://www.cnblogs.com/lhat/p/6819170.html
總結
以上是生活随笔為你收集整理的【Java基础总结】多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到蟒蛇是什么意思
- 下一篇: Java之杨辉三角的实现