112年警察人員特種考試資訊管理人員三等物件導向程式設計

四、以下Java 程式模擬銀行帳戶存款與查詢餘額的多執行緒功能。Account 是帳戶類別,可以存款與查詢餘額;ATM 類別可以操作帳戶的存款功能;Test 類別輸出查詢之最後帳戶餘額。

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

import java.util.ArrayList;

class Account {

    public void deposit(int m) { balance = balance + m; }

    public int getBalance( ) { return balance; }

    private int balance = 0;

}

class ATM extends Thread {

    public ATM(Account a, int m) { account = a; money = m; }

    public void run( ) {

    try {

        Thread.sleep(100);

    } catch (InterruptedException e) { e.printStackTrace( ); }

        synchronized(Account.class) { account.deposit(money); }

        // account.deposit(money);

    }

    private Account account;

    private int money;

}

public class Test {

    public static void main(String[ ] args) throws InterruptedException {

        ArrayList<Thread> ts = new ArrayList<>( );

        Account account = new Account( );

        for(int i = 1 ; i <= 10 ; i++) {

            Thread atm = new ATM(account, i);

            atm.start( );

            ts.add(atm);

        }

        for(Thread t:ts) { t.join( ); }

        System.out.println("" + account.getBalance( ));

    }

}

    ()請說明此程式的執行緒之特性以及此程式輸出。(10分)

    () Line 28 註解掉後,請說明程式執行結果與其運作原因。再將 Line 13 註解掉,並打開 Line 14, 28 註解後,請說明此時程式執行結果與其運作原因。(15分)

答:

import java.util.ArrayList;

// 帳戶類別,包含存款 (deposit) 和取得餘額 (getBalance) 的方法

class Account {

    public void deposit(int m) { balance = balance + m; } // 存款方法,將傳入的金額加到餘額上

    public int getBalance( ) { return balance; } // 取得餘額的方法,回傳當前的餘額

    private int balance = 0; // 初始餘額為0

}

// 自動提款機類別,繼承 Thread 類別,可以進行多執行緒操作

class ATM extends Thread {

    // 建構子,接收一個 Account 物件和一個金額作為存款

    public ATM(Account a, int m) { account = a; money = m; }

    public void run( ) { // 覆寫 Thread run 方法,會在呼叫 start( ) 時被執行

    try {

        Thread.sleep(100); // 讓當前的執行緒睡眠100毫秒

    } catch (InterruptedException e) {

        e.printStackTrace( ); // 捕捉和處理 InterruptedException 異常

    }

        // 在同步區塊中進行存款操作,以確保資料的一致性

        synchronized(Account.class) { account.deposit(money); }

        // account.deposit(money);

    }

    private Account account; // Account物件,代表一個帳戶

    private int money; // 存款金額

}

public class Test {

    public static void main(String[ ] args) throws InterruptedException {

        ArrayList<Thread> ts = new ArrayList<>( ); // 建立一個 Thread 物件的 ArrayList

        Account account = new Account( ); // 建立一個新的帳戶

        for(int i = 1 ; i <= 10 ; i++) { // 重複10

            // 建立一個 ATM 物件,並且傳入帳戶和存款金額 (依序從1增加到10)

            Thread atm = new ATM(account, i);

            atm.start( ); // 開始 ATM 執行緒,會呼叫 ATM run( ) 方法

            ts.add(atm); // ATM 物件加入 ArrayList

        }

        for(Thread t:ts) { t.join( ); } // 使用 join( ) 方法等待所有的 ATM 執行緒結束

        System.out.println("" + account.getBalance( )); // 輸出帳戶的最終餘額

    }

}

執行結果:

55

說明:

這個程式建立了一個帳戶和10個自動提款機 (ATM)。每個 ATM 會向該帳戶存入一定的金額 (110)。所有的 ATM 是在不同的執行緒中運行,因此它們的存款操作可能會同時進行。為了防止資料不一致 (race condition),在存款操作時使用了同步區塊 (synchronized) 來確保在同一時間只有一個執行緒可以操作帳戶。

()

1.執行緒的特性:

  (1)此程式是一個具有多執行緒 (Thread) Java 程式,其中每個 ATM 實例 是一個單獨的執行緒。在這個程式中,每個 ATM 執行緒會對同一個帳戶進行存款操作。考慮到多個執行緒可能同時進行存款操作,這裡使用了 Java synchronized 關鍵字來確保一次只有一個執行緒可以存款,這樣就可以避免資料不一致的情況發生。

  (2)這裡的synchronized(Account.class) 是一個同步區塊,代表一次只有一個執行緒可以進入此區塊的程式碼。由於存款操作在此同步區塊中,所以不會發生多個執行緒同時更改帳戶餘額的情況,這就確保了資料的一致性。

  (3)最後,for(Thread t:ts) { t.join( ); } 確保主執行緒會等待所有 ATM 執行緒完成後再繼續執行。join( ) 方法會讓當前執行緒 (指主執行緒) 暫停執行,直到該執行緒 (指每個 ATM 執行緒) 結束。

2.程式輸出:

  程式的輸出將會是所有 ATM 存款金額的總和,因為建立的 ATM 執行緒分別會向帳戶存入110的金額,所以輸出應該是1+2+3+...+10 = 55

()

1.Line 28註解掉後,程式執行結果與其運作原因:

  (1) for(Thread t:ts) { t.join( ); } 註解掉後,會造成主執行緒可能不會等待所有的 ATM 執行緒結束就繼續執行。也就是說,當主執行緒執行到 System.out.println("" + account.getBalance( )); 時,可能還有一些 ATM 執行緒尚未完成存款操作。

  (2)在多執行緒環境中,執行緒的執行順序是不確定的。如果不使用 join( ),那麼主執行緒並不會等待其他所有的 ATM 執行緒完成就會繼續執行。因此,System.out.println("" + account.getBalance( )); 可能在所有的存款操作完成之前就被執行,導致輸出的結果可能會小於55,即所有 ATM 存款的總和。

  (3)為了確保所有的 ATM 執行緒都完成存款操作,通常會在主執行緒中使用 join( ) 方法來等待其他執行緒的結束。如果這裡不使用 join( ),那麼主執行緒可能會在所有的ATM 執行緒結束之前就輸出帳戶的餘額,這樣就可能獲得不正確的結果。

2.Line 13註解掉,並打開Line 14, 28註解後,程式執行結果與其運作原因:

  (1) synchronized(Account.class) { account.deposit(money); } 註解掉後,每個 ATM 執行緒執行存款操作時就不會進行同步了。這表示同一時間可能有多個 ATM 執行緒同時對帳戶進行存款操作,這就可能會導致資料的不一致性,也稱為競爭狀態 (Race Condition)

  (2)例如,假設兩個 ATM 執行緒同時進行存款操作,首先讀取帳戶的當前餘額,然後將存款金額加到當前餘額上,最後將新的餘額寫回帳戶。如果這兩個執行緒的這三個操作重疊了,那麼可能會發生下面的情況:

     a.執行緒 A 及執行緒 B 讀取帳戶的當前餘額,假設都是10元。

     b.執行緒 A 將存款金額加到當前餘額上,假設存款金額是5元,那麼新的餘額是15元。

     c.執行緒 B 也將存款金額加到當前餘額上,假設存款金額是5元,但是它讀取的當前餘額還是10元,所以它計算出的新餘額也是15元。

     d.執行緒 A 將新的餘額15元寫回帳戶。

     e.執行緒 B 也將新的餘額15元寫回帳戶。

     f.最終結果是帳戶的餘額是15元,但是實際上應該是20 (原有的10+執行緒 A 存款5+執行緒 B 存款5)。這就是因為競爭狀態導致的資料不一致。

  (3)所以,如果將 synchronized(Account.class) { account.deposit(money); } 註解掉,程式輸出可能會小於55,這取決於執行緒的調度和執行順序。

arrow
arrow
    創作者介紹
    創作者 jacksaleok 的頭像
    jacksaleok

    國考資訊處理工作室(高考二級資訊處理/高考三級資訊處理/調查局三等/關務人員三等/地方特考三等)

    jacksaleok 發表在 痞客邦 留言(0) 人氣()