顯示具有 multi-thread 標籤的文章。 顯示所有文章
顯示具有 multi-thread 標籤的文章。 顯示所有文章

2015年8月4日 星期二

Java上課練習:多執行緒_同步化(synchronized)

同步化方法 ( synchronized )
多執行緒共享物件問題

進行多執行緒要注意同步的問題,當同時有多個執行緒進行時,若該程式需要某一執行緒執行完畢後,才能執行下一個執行緒,此時就需要進行同步化

製作一程式ATM,同時有三個執行緒進行提款
類別圖


程式碼
class Account {

    private int balance;//餘額

    //建構子
    public Account(int balance) {
        this.balance = balance;
    }

    //getter
    public int getBalance() {
        return balance;
    }

    //setter
    public void setBalance(int balance) {
        this.balance = balance;
    }

    //提款
    public void withdraw(int amount) { 
        this.balance = getBalance();//先取得Account帳戶還有多少餘額
        交易進行中();//模擬交易處理時間
        balance -= amount;//提款後餘額
        this.setBalance(balance);
    }

    //模擬交易處理時間
    private void 交易進行中() {
        int delay = (int) (Math.random() * 3000);
        try {
            Thread.sleep(delay);//讓執行緒暫停,模擬交易時間
        } catch (InterruptedException ex) {
            System.out.println(ex);
        }
    }

}

//執行緒
class ATM extends Thread {

    private Account account; //宣告

    //建構子
    public ATM(Account account) { //設定帳戶
        this.account = account;
    }

    public void run() {
        String atmName = Thread.currentThread().getName();//取得執行緒名稱

        System.out.println(atmName + "提款中...");
        account.withdraw(1);//對帳戶提款一元
        System.out.println(atmName + "完成提款,餘額:" + account.getBalance());

    }
}

public class ATMTTest {

    public static void main(String[] args) {
        Account acc = new Account(100); //設立帳戶,餘額100
        System.out.println("帳戶餘額:" + acc.getBalance());

        //建立三個執行緒,共同使用一個帳戶
        ATM atm1 = new ATM(acc);
        ATM atm2 = new ATM(acc);
        ATM atm3 = new ATM(acc);
        //設定執行緒名稱
        atm1.setName("ATM1");
        atm2.setName("ATM2");
        atm3.setName("ATM3");
        //啟動        
        atm1.start();
        atm2.start();
        atm3.start();

    }

}

顯示結果


因為沒有進行同步化,所以3台ATM同時提款,這邊要修改為當有ㄧ台ATM在執行,其他兩台ATM要先暫停

同步化方法只允許一個執行緒在其中執行,只要有執行緒在同步化方法 中,其他執行緒必須等候
class Account {

    private int balance;//餘額

    //建構子
    public Account(int balance) {
        this.balance = balance;
    }

    //getter
    public int getBalance() {
        return balance;
    }

    //setter
    public void setBalance(int balance) {
        this.balance = balance;
    }  
    /*
     同步化方法 ( synchronized )
     同步化方法只允許一個執行緒在其中執行,只要有執行緒在同步化方法 中,其他執行緒必須等候
     */
    //提款
    public void withdraw(int amount) { //這邊可以在最前方加入同步化(synchronized)或是在執行緒內容加入
        this.balance = getBalance();//先取得Account帳戶還有多少餘額
        交易進行中();//模擬交易處理時間
        balance -= amount;//提款後餘額
        this.setBalance(balance);
    }

    //模擬交易處理時間
    private void 交易進行中() {
        int delay = (int) (Math.random() * 3000);
        try {
            Thread.sleep(delay);//讓執行緒暫停,模擬交易時間
        } catch (InterruptedException ex) {
            System.out.println(ex);
        }
    }
}

//執行緒
class ATM extends Thread {

    private Account account; //宣告

    //建構子
    public ATM(Account account) { //設定帳戶
        this.account = account;
    }

    public void run() {
        String atmName = Thread.currentThread().getName();//取得執行緒名稱
        //使用同步化synchronized
        //因為多個ATM對同一個帳戶提款需要前一個動作完成才能給下一個提款,所以需要做同步化獨佔
        synchronized(account){ //鎖定account物件(獨佔)
        System.out.println(atmName + "提款中...");
        account.withdraw(1);//對帳戶提款一元
        System.out.println(atmName + "完成提款,餘額:" + account.getBalance());
        }
        //未同步化
//        System.out.println(atmName + "提款中...");
//        account.withdraw(1);//對帳戶提款一元
//        System.out.println(atmName + "完成提款,餘額:" + account.getBalance());

    }
}

public class ATMTTest {

    public static void main(String[] args) {
        Account acc = new Account(100); //設立帳戶,餘額100
        System.out.println("帳戶餘額:" + acc.getBalance());

        //建立三個執行緒,共同使用一個帳戶
        ATM atm1 = new ATM(acc);
        ATM atm2 = new ATM(acc);
        ATM atm3 = new ATM(acc);
        //設定執行緒名稱
        atm1.setName("ATM1");
        atm2.setName("ATM2");
        atm3.setName("ATM3");
        //啟動        
        atm1.start();
        atm2.start();
        atm3.start();

    }
}

顯示結果


這邊有兩種方式,ㄧ種是直接在Account的提款mothod最前方加入synchronized,另一種是採用同步化區塊 ( synchronized blocks ),撰寫程式的彈性較大,執行緒可指定要獨佔哪個物件,並設定同步化程式的範圍

Java上課練習:多執行緒_實作 Runnable 介面

實作 Runnable 介面,比繼承 Thread 更具彈性

Java 類別只能單一繼承,盡量採用實作 Runnable,將 Runnable 交給 Thread 執行

類別圖


多執行緒(Thread)的程式使用Runnable改寫,使用兩個執行緒處理一個程式
class 任務 extends Thread{
    int i;
    public void run(){
        for(;i<100;i++){
            String name=Thread.currentThread().getName();//取得目前執行緒的名稱
            System.out.println(name+":"+i);
        }
    }           
}

public class Test3 {

    public static void main(String[] args) {
        任務 task =new 任務(); 
        //兩個執行緒共用一個task
        Thread t1=new Thread(task);//Thread-0
        Thread t2=new Thread(task);//Thread-1
        t1.start();
        t2.start();

    }
}

顯示結果


Q:為什麼 i 沒有連續?
Ans:因為 i++ 並非原子性操作,電腦的運算往往不是一個動作能完成
           i ++  看似是一個動作,實際上需要三個動作
            1. 將記憶體資料放入CPU
        2. CPU執行加1
        3. 將CPU運算結果放回記憶體

Java上課練習:多執行緒_下載檔案

下載檔案使用多執行緒


import java.io.*;
import java.net.URL;
import java.nio.file.*;

//建立執行緒
class 小弟 extends Thread {

    private String id; //下載檔案的id
    private String saveFileName;//下載後儲存檔案的名稱

    public 小弟(String id, String saveFileName) {
        this.id = id;
        this.saveFileName = saveFileName;
    }

    @Override
    public void run() {
        copyFileFromGoogleDrive();
    }

    public void copyFileFromGoogleDrive() {
        try {
            System.out.println("下載檔案..." + id);
            URL url = new URL("https://drive.google.com/uc?id=" + id);
            InputStream is = url.openStream();
            Path savePath = Paths.get(saveFileName);//存檔路徑
            Files.copy(is, savePath, StandardCopyOption.REPLACE_EXISTING);//檔案存在就覆蓋
            System.out.println("存檔成功" + saveFileName);
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}

public class 多執行緒下載檔案Test {

    public static void main(String[] args) {

        小弟 a = new 小弟("0BwIdzAjvQ8FwYVMxQUhXQUV2b0k", "C:/Users/Administrator/Desktop/MyJava/android-0.png");
        小弟 b = new 小弟("0BwIdzAjvQ8FwX1FzZDI4UHUwUXM", "C:/Users/Administrator/Desktop/MyJava/android-1.png");
        小弟 c = new 小弟("0BwIdzAjvQ8FwN2kzVklxTmF3QmM", "C:/Users/Administrator/Desktop/MyJava/asciiart.txt");
        a.start();
        b.start();
        c.start();
    }
}
執行結果


2015年8月3日 星期一

Java上課練習:多執行緒

多執行緒(multi-thread)

可以同時執行多個程式區塊,使程式執行的效率變高

範例:
列印100次Hello(單執行緒)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Test1 {
    
    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {
            System.out.println("MAin Hello " + i);
        }

    }

}
顯示結果:
MAin Hello 0
MAin Hello 1
MAin Hello 2
MAin Hello 3
MAin Hello 4
.....
MAin Hello 97
MAin Hello 98
MAin Hello 99

多執行緒:建立一個hello class 繼承 Thread,將迴圈寫在執行緒的實作mothod
在原本main迴圈下,新增兩組執行緒
有三個執行緒在執行,main 執行緒執行 main() ,建立了另外兩個執行緒 h1 與 h2
h1和h2沒有先後之分,完全取決於程式
且每次執行結果不會一樣,每個執行緒分配到多少時間是不固定的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//建立執行緒
class Hello extends Thread {

    public void run() { //執行緒實作的方法,工作內容
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+" hello " + i);
        }

    }
}

public class Test1 {
    
    public static void main(String[] args) {
        //main 執行緒執行 main() 
        //main()
        for (int i = 0; i < 100; i++) {
            System.out.println("MAin Hello " + i);
        }
        
        //建立 h1 執行緒
        Hello h1 = new Hello();
        h1.setName("h1"); //指定執行緒名稱
        //h1.run(); //呼叫,使用此方法還是單執行緒
        h1.start(); //啟動 h1 執行緒,不是呼叫 Thread,而是要啟動
        
        //建立 h2 執行緒
        Hello h2 = new Hello();
        h2.setName("h2");
        h2.start();

    }

}
顯示結果
MAin Hello 97
MAin Hello 98
MAin Hello 99
h2 hello 0
h1 hello 0
h2 hello 1

當執行緒 start( ) 後,不能重複啟動,否則會導致 IlleagalThreadStateException 例外

多執行緒摘要

  • 執行順序執行順序 與 程式開工(啟動)順序無關,由系統控制
  • 每個執行緒所分配到的 CPU 時間不是固定的,由系統控制
  • 執行緒啟動後,由系統分配時間執行 run( ) ,我們不會自己主動去呼叫 run( )
  • 主類別 main() 方法的執行,是由系統產生的 main執行緒 執行
  • 執行緒 start( ) 後,不能重複啟動,會導致例外
  • 取得執行當前程式碼的執行緒
    Thread.currentThread()
  • 取得 與 設定 執行緒物名稱
    getName( )
    setName( )