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; } } } } }售票
結果:
三、實驗總結
這是本學期最後一次實驗,實驗內容也比較少,對線程有了更深的認識,很感謝學長和老師這一學期幫助。
201771010108 -韓臘梅-第十七周學習總結