1. 程式人生 > >張雲飛 201771010143 《面對物件程式設計(java)》第十七週學習總結

張雲飛 201771010143 《面對物件程式設計(java)》第十七週學習總結

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);//將initialBalance分配給accounts陣列的每個元素。
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();//返回繫結到此 Lock 例項的新 Condition 例項
}

/**
* 將資金從一個帳戶轉移到另一個帳戶
* @param from 帳戶轉帳來自
* @param to 帳戶轉帳到
* @param amount 轉賬金額
*/
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

 

實驗結果:

 

測試程式2:

l 在Elipse環境下除錯教材655頁程式14-8,結合程式執行結果理解程式;

l 掌握synchronized在多執行緒同步中的應用。

實驗結果:

 

測試程式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();

  }

}

修改後的程式碼:

 

package a;

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();

}

}

實驗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張票

 程式碼:

public class Demo {

public static void main(String[] args) {
// TODO Auto-generated method stub
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 x = 1;
boolean f = true;

public void run() {
while (f) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (this) {
if (x <= 10) {
System.out.println(Thread.currentThread().getName() + "視窗售:第" + x + "張票");
x++;
} else
f = false;
}

}

}
}

Demo

結果:

 

本週學習總結:

5、執行緒的建立和啟動

    A、[重點]繼承Thread類或實現Runnable介面,重寫或實現run方法,run方法代表執行緒要完成的任務

    B、建立Thread子類或是Runnable的實現類,即建立的執行緒物件;不同的是介面實現執行緒,

        需要將介面的實現類作為引數傳遞給Thread類的構造引數

    C、用執行緒物件的start方法啟動執行緒

 

6、繼承Thread和實現Runnable介面建立執行緒的區別

Java之多執行緒流學習總結-相關操作

    採用Runnable介面實現執行緒:

    優勢:

        A、執行緒類只是實現了Runnable介面,還可以繼承其他的類

        B、在這種方式下,可以多個執行緒共享同一個目標物件,所以很合適多個執行緒來處理同一份資源的情況,

            從而可以將CPU、程式碼和資料分開,形成清晰的模型,較好的面相物件思想。

    劣勢:程式設計稍微複雜,如果需要訪問當前執行緒需要用Thread.currentThread方法來獲取

 

    採用繼承Thread類的方式實現執行緒:

    優勢:編寫簡單,如果要獲得當前執行緒直接this即可

    劣勢:執行緒類繼承了Thread,不能在繼承其他類

    相對而言,用Runnable的方式更好,具體可以根據當前需要而定;

 

7、執行緒生命週期

Java之多執行緒流學習總結-相關操作

    執行緒被建立啟動後,不併不是啟動後就進入了執行狀態,也不是一直處於的執行狀態。

    執行緒的生命週期分為建立(new)、就緒(Runnable)、執行(running)、阻塞(Blocked)、死亡(Dead)五種狀態。

    執行緒啟動後不會一直霸佔CPU資源,所以CPU需要在多條執行緒中切換執行,執行緒就會在多次的執行和阻塞中切換。

 

8、新建(new)和就緒(Runnable)狀態

    當new一個執行緒後,該執行緒處於新建狀態,此時它和Java物件一樣,僅僅由Java虛擬機器為其分配記憶體空間,並初始化成員變數。

    此時執行緒物件沒有表現出任何的動態特徵,程式也不會執行執行緒的執行體。

    注意:run方法是執行緒的執行體,不能由我們手動呼叫。我們可以用start方法啟動執行緒,系統會把run方法當成執行緒的執行體來執行,

    如果直接呼叫執行緒物件run方法,則run方法立即會被執行。而且在run方法返回之前其他執行緒無法並行執行,

    也就是說系統會把當前執行緒類當成一個普通的Java物件,而run方法也是一個普通的方法,而不是執行緒的執行體。

 

9、執行(running)和阻塞(Blocked)狀態

    如果處於就緒狀態的執行緒就獲得了CPU,開始執行run方法的執行緒執行體,則該執行緒處於執行狀態。

    單CPU的機器,任何時刻只有一條執行緒處於執行狀態。當然,在多CPU機器上將會有多執行緒並行(parallel)執行,

    當執行緒大於CPU數量時,依然會在同一個CPU上切換執行。

    執行緒執行機制:一個執行緒執行後,它不可能一直處於執行狀態(除非它執行的時間很短,瞬間執行完成),執行緒在執行過程中需要中斷,

    目的是讓其他的執行緒有執行機會,執行緒的排程取決於底層的策略。對應搶佔式的系統而言,系統會給每個可執行的執行緒一個小時間段來處理任務,

    當時間段到達系統就會剝奪該執行緒的資源,讓其他的執行緒有執行的機會。在選擇下一個執行緒時,系統會考慮執行緒優先順序。

    以下情況會出現執行緒阻塞狀態:

        A、執行緒呼叫sleep方法,主動放棄佔用的處理器資源

        B、執行緒呼叫了阻塞式IO方法,在該方法返回前,該執行緒被阻塞

        C、執行緒試圖獲得一個同步監視器,但該同步監視器正被其他執行緒所持有。

        D、執行緒等待某個通知(notify)

        E、程式呼叫了suspend方法將該執行緒掛起。不過這個方法容易導致死鎖,儘量不免使用該方法

    當執行緒被阻塞後,其他執行緒將有機會執行。被阻塞的執行緒會在合適的時候重新進入就緒狀態,注意是就緒狀態不是執行狀態。

    也就是被阻塞執行緒在阻塞解除後,必須重新等待執行緒排程器再次呼叫它。

    針對上面執行緒阻塞的情況,發生以下特定的情況可以解除阻塞,讓程序進入就緒狀態:

        A、呼叫sleep方法的經過了指定的休眠時間

        B、執行緒呼叫的阻塞IO已經返回,阻塞方法執行完畢

        C、執行緒成功獲得了試圖同步的監視器

        D、執行緒正在等待某個通知,其他執行緒發出了通知

        E、處於掛起狀態的執行緒呼叫了resume恢復方法

    執行緒從阻塞狀態只能進入就緒狀態,無法進入執行狀態。而就緒和執行狀態之間的轉換通常不受程式控制,而是由系統排程所致的。

    當就緒狀態的執行緒獲得資源時,該執行緒進入執行狀態;當執行狀態的執行緒事情處理器資源時就進入了就緒狀態。

    但對呼叫了yield的方法就例外,此方法可以讓執行狀態轉入就緒狀態。

 

10、執行緒死亡(Dead)狀態

    執行緒會在以下方式進入死亡狀態:

    A、run方法執行完成,執行緒正常結束

    B、執行緒丟擲未捕獲的異常或Error

    C、直接呼叫該執行緒的stop方法來結束執行緒—該方法易導致死鎖,注意使用

    注意:當主執行緒結束的時候,其他執行緒不受任何影響。一旦子執行緒啟動後,會擁有和主執行緒相同的地位,不受主執行緒影響。

    isAlive方法可以測試當前執行緒是否死亡,當執行緒處於就緒、執行、阻塞狀態,該方法返回true,如果執行緒處於新建或死亡狀態就會返回false。

    不要試圖對死亡的執行緒呼叫start方法,來啟動它。死亡執行緒不可能再次執行。

 

11、控制執行緒

    Java執行緒提供了很多工具方法,這些方法都很好的控制執行緒

    A、join執行緒

        讓一個執行緒等待另一個執行緒完成的方法。當某個程式執行流中呼叫其他執行緒的join方法時,呼叫執行緒將會被阻塞,直到被join方法的join執行緒執行完成為止。

        join方法通常有使用執行緒的程式呼叫,將大問題劃分成許多小問題。每個小問題分配一個執行緒。當所有的小問題得到處理後,再呼叫主執行緒進一步操作。

        join有三種過載模式:

            一、join等待被join的執行緒執行完成

            二、join(long millis)等待被join的執行緒時間最長為millis毫秒,如果在millis毫秒外,被join的執行緒還沒有執行完則不再等待

            三、join(long millis, int nanos)被join的執行緒等待時間長為millis毫秒加上nanos微秒

        通常我們很少用第三種join,原因有二:程式對時間的精度無需精確到千分之一毫秒

        計算機硬體、作業系統也無法做到精確到千分之一毫秒

   

    B、後臺執行緒

        有一種執行緒,在後臺執行,它的任務是為其他執行緒提供服務,這種執行緒被稱為“後臺執行緒(Daemon Thread)”,有被稱為“守護執行緒”或“精靈執行緒”。

        JVM的垃圾回收器執行緒就是後臺程序。

        後臺程序有個特徵是:如果前臺的程序都死亡,那麼後臺程序也死亡。(它為前臺程序服務)

        用Thread的setDaemon (true)方法可以指定當前執行緒為後臺執行緒。

        注意:前臺執行緒執行完成死亡後,JVM會通知後臺執行緒,後臺執行緒就會死亡。但它得到通知到後臺執行緒作成響應,需要一段時間,

        而且要將某個執行緒設定為後臺執行緒,必需要在該執行緒啟動前設定,也就是說設定setDaemon必需在start方法前面呼叫。

        否則會出現java.lang.IllegalThreadStateException異常

   

    C、執行緒休眠sleep

        如果需要當前執行緒暫停一段時間,並進入阻塞狀態就需要用sleep,sleep有2中過載方式:

        sleep(long millis)讓當前執行緒暫停millis毫秒後,並進入阻塞狀態,該方法受系統計時器和執行緒排程器的影響

        sleep(long millis, int nanos)讓當前正在執行的執行緒暫停millis毫秒+nanos微秒,並進入阻塞

        當呼叫sleep方法進入阻塞狀態後,在sleep時間段內,該執行緒不會獲得執行機會,即使沒有其他可執行的執行緒,處於sleep的執行緒不會執行。

 

    D、執行緒讓步yield

        yield和sleep有點類似,它也可以讓當前執行的執行緒暫停,但它不會阻塞執行緒,只是將該執行緒轉入到就緒狀態。

        yield只是讓當前執行緒暫停下,讓系統執行緒排程器重新排程下。

        當yield的執行緒後,當前執行緒暫停。系統執行緒排程器會讓優先順序相同或是更高的執行緒執行。

       

        sleep和yield的區別

            (1)、sleep方法暫停當前執行緒後,會給其他執行緒執行集合,不會理會執行緒的優先順序。但yield則會給優先順序相同或高優先順序的執行緒執行機會

            (2)、sleep方法會將執行緒轉入阻塞狀態,直到經過阻塞時間才會轉入到就緒狀態;而yield則不會將執行緒轉入到阻塞狀態,它只是強制當前執行緒進入就緒狀態。

                    因此完全有可能呼叫yield方法暫停之後,立即再次獲得處理器資源繼續執行。

            (3)、sleep宣告丟擲了InterruptedException異常,所以呼叫sleep方法時,要麼捕獲異常,要麼丟擲異常。而yield沒有申明丟擲任何異常

       

    E、改變執行緒優先順序

        每個執行緒都有優先順序,優先順序決定執行緒的執行機會的多少。

        每個執行緒預設和它建立的父類的優先順序相同,main方法的優先順序是普通優先順序,那在main方法中建立的子執行緒都是普通優先順序。

        getPriority(int newPriority)/setPriority(int)

        設定優先順序有以下級別:

            MAX_PRIORITY 值是10

            MIN_PRIORITY 值是1

            NORM_PRIORITY 值是5

            範圍是1-10;