201771010108 -韓臘梅-第十七週學習總結
第十七週學習總結
一、知識總結
1.建立執行緒的2種方法
方式1:繼承java.lang.Thread類,並覆蓋run()方法。優勢:編寫簡單;劣勢:無法繼承其他父類
方式2:實現java.lang.Runnable介面,並實現run()方法。優勢:可以繼承其他類,多執行緒可以共享同一個Thread物件;劣勢:程式設計方式稍微複雜,如需訪問當前執行緒,需呼叫Thread.currentThread()方法
2. Java建立執行緒後,呼叫start()方法和run()的區別
兩種方法的區別
1) start:
用start方法來啟動執行緒,真正實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢而直接繼續執行下面的程式碼。通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到cpu時間片,就開始執行run()方法,這裡方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容,Run方法執行結束,此執行緒隨即終止。
2) run:
run()方法只是類的一個普通方法而已,如果直接呼叫run方法,程式中依然只有主執行緒這一個執行緒,其程式執行路徑還是隻有一條,還是要順序執行,還是要等待
run方法體執行完畢後才可繼續執行下面的程式碼,這樣就沒有達到寫執行緒的目的。
總結:呼叫start方法方可啟動執行緒,而run方法只是thread的一個普通方法呼叫,還是在主執行緒裡執行。
這兩個方法應該都比較熟悉,把需要並行處理的程式碼放在run()方法中,start()方法啟動執行緒將自動呼叫 run()方法,這是由jvm的記憶體機制規定的。並且run()方法必須是public訪問許可權,返回值型別為void。
兩種方式的比較 :
實際中往往採用實現Runable介面,一方面因為java只支援單繼承,繼承了Thread類就無法再繼續繼承其它類,而且Runable介面只有一個run方法;另一方面通過結果可以看出實現Runable接口才是真正的多執行緒。
3.執行緒的生命週期
執行緒是一個動態執行的過程,它也有一個從生產到死亡的過程。
(1)生命週期的五種狀態
新建(new Thread)
當建立Thread類的一個例項(物件)時,此執行緒進入新建狀態(未被啟動)。
例如:Thread t1 = new Threade();
就緒(runnable)
執行緒已經被啟動(start),正在等待分配CPU時間片,也就是說此事執行緒正在就緒佇列中排隊等候得到CPU資源。
例如:t1.start();
執行(running)
執行緒獲得cpuz資源正在執行任務(run()方法),此時除非執行緒自動放棄CPU資源或者有優先順序更高的的執行緒進入,執行緒將一直執行到結束!
死亡(dead)
當執行緒執行完畢或被其它執行緒殺死,執行緒就進入死亡狀態,這時執行緒不可能再進入就緒狀態等待執行。
自然終止:正常執行run()方法後終止
異常終止:呼叫stop()方法讓一個執行緒終止執行
堵塞(blocked)
由於某種原因導致正在執行的執行緒讓出CPU並暫停自己的執行,即進入堵塞狀態。
正在睡眠:用sleep(long t) 方法可使執行緒進入睡眠方式。一個睡眠著的執行緒在指定的時間過去可進入就緒狀態。
正在等待:呼叫wait()方法。(呼叫notify()方法回到就緒狀態)
被另一個執行緒所阻塞:呼叫suspend()方法。(呼叫resume()方法恢復)
5.如何實現執行緒同步?
當多個執行緒訪問同一個資料時,容易出現執行緒安全問題,需要某種方式來確保資源在某一時刻只被一個執行緒使用。需要讓執行緒同步,保證資料安全
執行緒同步的實現方案:同步程式碼塊和同步方法,均需要使用synchronized關鍵字
執行緒同步的好處:解決了執行緒安全問題
執行緒同步的缺點:效能下降,可能會帶來死鎖
6. 關於同步鎖的更多細節
Java中每個物件都有一個內建鎖。
當程式執行到非靜態的synchronized同步方法上時,自動獲得與正在執行程式碼類的當前例項(this例項)有關的鎖。獲得一個物件的鎖也稱為獲取鎖、鎖定物件、在物件上鎖定或在物件上同步。
當程式執行到synchronized同步方法或程式碼塊時才該物件鎖才起作用。
一個物件只有一個鎖。所以,如果一個執行緒獲得該鎖,就沒有其他執行緒可以獲得鎖,直到第一個執行緒釋放(或返回)鎖。這也意味著任何其他執行緒都不能進入該物件上的synchronized方法或程式碼塊,直到該鎖被釋放。
釋放鎖是指持鎖執行緒退出了synchronized同步方法或程式碼塊。
關於鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變數和類;
2)、每個物件只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個物件上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個執行緒要執行一個類中的synchronized方法,並且兩個執行緒使用相同的例項來呼叫方法,那麼一次只能有一個執行緒能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個執行緒在物件上獲得一個鎖,就沒有任何其他執行緒可以進入(該物件的)類中的任何一個同步方法。
5)、如果執行緒擁有同步和非同步方法,則非同步方法可以被多個執行緒自由訪問而不受鎖的限制。
6)、執行緒睡眠時,它所持的任何鎖都不會釋放。
7)、執行緒可以獲得多個鎖。比如,在一個物件的同步方法裡面呼叫另外一個物件的同步方法,則獲取了兩個物件的同步鎖。
8)、同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分程式碼塊。
9)、在使用同步程式碼塊時候,應該指定在哪個物件上同步,也就是說要獲取哪個物件的鎖。例如:
public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}
當然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:
public synchronized int getX() {
return x++;
}
與
public int getX() {
synchronized (this) {
return x;
}
}
效果是完全一樣的。
7. 簡述sleep( )和wait( )有什麼區別?
sleep()是讓某個執行緒暫停執行一段時間,其控制範圍是由當前執行緒決定,也就是說,線上程裡面決定.好比如說,我要做的事情是 "點火->燒水->煮麵",而當我點完火之後我不立即燒水,我要休息一段時間再燒.對於執行的主動權是由我的流程來控制。
而wait(),首先,這是由某個確定的物件來呼叫的,將這個物件理解成一個傳話的人,當這個人在某個執行緒裡面說"暫停!",也是 thisObj.wait(),這裡的暫停是阻塞,還是"點火->燒水->煮飯",thisObj就好比一個監督我的人站在我旁邊,本來該線 程應該執行1後執行2,再執行3,而在2處被那個物件喊暫停,那麼我就會一直等在這裡而不執行3,但正個流程並沒有結束,我一直想去煮飯,但還沒被允許, 直到那個物件在某個地方說"通知暫停的執行緒啟動!",也就是thisObj.notify()的時候,那麼我就可以煮飯了,這個被暫停的執行緒就會從暫停處 繼續執行。
其實兩者都可以讓執行緒暫停一段時間,但是本質的區別是一個執行緒的執行狀態控制,一個是執行緒之間的通訊的問題。
二、實驗部分——執行緒同步控制
1、實驗目的與要求
(1) 掌握執行緒同步的概念及實現技術;
(2) 執行緒綜合程式設計練習
2、實驗內容和步驟
實驗1:測試程式並進行程式碼註釋。
測試程式1:
l 在Elipse環境下除錯教材651頁程式14-7,結合程式執行結果理解程式;
l 掌握利用鎖物件和條件物件實現的多執行緒同步技術。
package synch; import java.util.*; import java.util.concurrent.locks.*; /** 一個銀行有許多銀行帳戶,使用鎖序列化訪問 * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; private Lock bankLock; private Condition sufficientFunds; /** * 建設銀行。 * @param n 賬號 * @param initialBalance 每個賬戶的初始餘額 */ public Bank(int n, double initialBalance) { accounts = new double[n]; Arrays.fill(accounts, initialBalance); bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); } /** * 把錢從一個賬戶轉到另一個賬戶。 * @param 從賬戶轉賬 * @param 轉到要轉賬的賬戶 * @param 請允許我向你轉達 */ public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount) sufficientFunds.await(); System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); } finally { bankLock.unlock(); } } /** * 獲取所有帳戶餘額的總和。 * @return 總餘額 */ public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a : accounts) sum += a; return sum; } finally { bankLock.unlock(); } } /** * 獲取銀行中的帳戶數量。 * @return 賬號 */ public int size() { return accounts.length; } }Bank
package synch; /** * 這個程式顯示了多個執行緒如何安全地訪問資料結構。 * @version 1.31 2015-06-21 * @author Cay Horstmann */ public class SynchBankTest { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static final double MAX_AMOUNT = 1000; public static final int DELAY = 10; public static void main(String[] args) { Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable r = () -> { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.start(); } } }SynchBankTest
測試程式2:
l 在Elipse環境下除錯教材655頁程式14-8,結合程式執行結果理解程式;
l 掌握synchronized在多執行緒同步中的應用。
package synch2; import java.util.*; /** * 具有多個使用同步原語的銀行賬戶的銀行。 * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; /** * 建設銀行。 * @param n 賬號 * @param initialBalance 每個賬戶的初始餘額 */ public Bank(int n, double initialBalance) { accounts = new double[n]; Arrays.fill(accounts, initialBalance); } /** * 把錢從一個賬戶轉到另一個賬戶。 * @param 從賬戶轉賬 * @param 轉到要轉賬的賬戶 * @param 請允許我向你轉達 */ public synchronized void transfer(int from, int to, double amount) throws InterruptedException { while (accounts[from] < amount) wait(); System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); notifyAll(); } /** * 獲取所有帳戶餘額的總和。 * @return 總餘額 */ public synchronized double getTotalBalance() { double sum = 0; for (double a : accounts) sum += a; return sum; } /** * 獲取銀行中的帳戶數量。 * @return */ public int size() { return accounts.length; } }Bank
package synch2; /** * * 這個程式展示了多個執行緒如何使用同步方法安全地訪問資料結構。 * @version 1.31 2015-06-21 * @author Cay Horstmann */ public class SynchBankTest2 { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static final double MAX_AMOUNT = 1000; public static final int DELAY = 10; public static void main(String[] args) { Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable r = () -> { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.start(); } } }SynchBankTest2
測試程式3:
l 在Elipse環境下執行以下程式,結合程式執行結果分析程式存在問題;
l 嘗試解決程式中存在問題。
class Cbank { private static int s=2000; public static void sub(int m) { int temp=s; temp=temp-m; try { Thread.sleep((int)(1000*Math.random())); } catch (InterruptedException e) { } s=temp; System.out.println("s="+s); } }
class Customer extends Thread { public void run() { for( int i=1; i<=4; i++) Cbank.sub(100); } } public class Thread3 { public static void main(String args[]) { Customer customer1 = new Customer(); Customer customer2 = new Customer(); customer1.start(); customer2.start(); } } |
執行結果:
改正程式碼:
class Cbank { private static int s=2000; public synchronized static void sub(int m) { int temp=s; temp=temp-m; try { Thread.sleep((int)(1000*Math.random())); } catch (InterruptedException e) { } s=temp; System.out.println("s="+s); } } class Customer extends Thread { public void run() { for( int i=1; i<=4; i++) Cbank.sub(100); } } public class Thread3 { public static void main(String args[]) { Customer customer1 = new Customer(); Customer customer2 = new Customer(); customer1.start(); customer2.start(); } }Cbank
實驗2 程式設計練習
利用多執行緒及同步方法,編寫一個程式模擬火車票售票系統,共3個視窗,賣10張票,程式輸出結果類似(程式輸出不唯一,可以是其他類似結果)。
Thread-0視窗售:第1張票
Thread-0視窗售:第2張票
Thread-1視窗售:第3張票
Thread-2視窗售:第4張票
Thread-2視窗售:第5張票
Thread-1視窗售:第6張票
Thread-0視窗售:第7張票
Thread-2視窗售:第8張票
Thread-1視窗售:第9張票
Thread-0視窗售:第10張票
程式碼:
package 程式設計一; public class 售票 { public static void main(String[] args) { mythread mythread=new mythread(); Thread t1=new Thread(mythread); Thread t2=new Thread(mythread); Thread t3=new Thread(mythread); t1.start(); t2.start(); t3.start(); } } class mythread implements Runnable{ int t=1; boolean flag=true; @Override public void run() { while(flag) { try { Thread.sleep(50); }catch(InterruptedException e) { e.printStackTrace(); } synchronized (this) { if(t<=10) { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName() + "視窗售:第" + t + "張票"); t++; } if(t>10) { flag=false; } } } } }售票
結果:
三、實驗總結
這是本學期最後一次實驗,實驗內容也比較少,對執行緒有了更深的認識,很感謝學長和老師這一學期幫助。