Multithread 多執行緒 I/O Lecturer:楊昌樺
Important Features of Java 何謂多工 (Multi-tasking) 例如,撰寫網路程式,模擬程式。 Java 利用 “執行緒” (Thread) 來實作多工 可將一個Process分成數份, 讓各部份能同時被執行。
Process and Thread Process - 正在執行的應用程式 不同的行程會對應到不同的 系統資源 CPU時間 Multithread的概念特別針對CPU時間的分配 暫時閒置的CPU時間,用在執行應用程式的其他工作上 語法不難,難在掌控 執行先後順序 資源維護
執行緒的生命週期 Blocked start() Runnable Running yield() notify(), I/O unblock sleep(), wait(), , I/O block start() Runnable Running yield()
設計背景 雖然說一次可以處理多個執行緒 不過在同一個時間點真正執行的還是只有一個 (例外:多CPU系統可以執行多個) 處理多個執行緒時 一次只有一個執行緒在執行(Running) 其他執行緒在Runnable區塊中等待 執行先後順序會依照執行緒的優先權來判定 沒執行的在Runnable區塊中等待下一次優先權的判定
Thread類別 java.lang.Thread 提供的基本方法 public class Thread extends Object implements Runnable 提供的基本方法 static yield() 讓目前running的暫停, 讓runnable的擇一跑 static sleep() 讓目前running的睡一個設定的時間 start() 啟動, 之後JVM可啟動該thread的run() setPriority() setPriority(MAX_PRIORITY):給最大優先權 setPriority(MIN_PRIORITY):給最小優先權 setPriority(NORM_PRIORITY):預設的優先權
簡單程式範例 (MyThread.java) public class MyThread extends Thread{ MyThread(String n) { super(n); } public void run() String tname = super.getName(); try for(int i = 1; i<=5000;i++) System.out.println(tname+" "+i+" "+Math.random()); catch(Exception e) System.out.println("error"); class TestThread { public static void main(String[] args) MyThread t1 = new MyThread("t1"); MyThread t2 = new MyThread("t2"); t1.start(); t2.start(); } thread t1, t2 各執行5000次迴圈,在各個電腦產生的執行順序也會因為各個電腦當時的CPU效能而有所不同
說明 新的執行緒物件(new MyThread) 經由start()方法進入Runnable 區準備執行 排程後將選出來的執行緒丟入Running區執行 此時會觸發執行緒run()方法,執行實作內容(寫在run()內的程式碼) 若執行緒未執行完成(例如取得的CPU時間已到卻還沒完成工作,或是程式呼叫了特定的方法) ,或是執行wait(),將會觸發Blocking event並進入Blocked區 被封鎖的執行緒解除封鎖(Unblocked)或者是或別的程式中呼叫了notify()方法之後,將再進入Runnable pool中等待下一次執行 若程式中呼叫了yield()方法(讓與),則是將執行權先給其他執行緒執行,不過其本身仍然還是在Runnable中
不能多重繼承時 (MyThread2.java) public class MyThread2 implements Runnable { int i; public void run() i = 0; while (true) System.out.println(Thread.currentThread().getName() + " " + (++i) + " " +Math.random()); if ( i == 5000 ) break; } class TestThread2 { public static void main(String args[]) MyThread2 r = new MyThread2(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.setName(“t1"); t2.setName(“t2"); t1.start(); t2.start(); } 先宣告一個空的MyThread2類別物件r 建立t1, t2 執行緒物件實體,其對象為 MyThread2中的run()方法
如果執行緒間彼此有同步的考量 (MyThread3.java) public class MyThread3 implements Runnable{ static int max=0; public void run() { for(int i = 1; i<=5000;i++) int tmp=max; System.out.println( Thread.currentThread().getName() + " " + ++tmp ); max=tmp; } class TestThread3 { public static void main(String args[]) MyThread3 r = new MyThread3(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } 各把max拿來加5000次 最後卻不一定等於10000 而且每次都不一樣, why?
方法加 synchronized 保證 addMax()這個方法 只有同時間只有一個process可以執行 public class MyThread4 implements Runnable{ static int max=0; public void run() { for(int i = 1; i<=5000;i++) addMax(); } private synchronized void addMax() int tmp=max; System.out.println(Thread.currentThread().getName() + " " + ++tmp); max=tmp; 保證 addMax()這個方法 只有同時間只有一個process可以執行
Your Turn 寫一個多執行緒程式,模擬三個人存同一個戶頭的錢,每次存1000圓,總共存10次 每次存完讓該Thread睡個幾毫秒 Thread.sleep((long)(Math.random()*10)); 這個方法需要處理exception,試用教過的例外處理 最後輸出的是隨機的存款出現的順序 確保最後出現的是三萬元 (可以每次顯示是誰存的,存多少,總額)
I/O Overview of I/O Streams Using the Streams File Streams Wrap Streams InputStreams Your Turn Standard in and out Streams Pipe Streams SequenceInputStream Filter Streams Random Access File
Overview of I/O Streams
Overview of I/O Streams 讀取與寫出資料的演算法: 讀取 寫出 開啟資料流 while 還有資料 讀取資料 關閉資料流 寫出資料 在 Java 中,處理 IO 必須要 import java.io 這個 package 根據資料型態(字元或位元),可以分成二類:
Overview of I/O Streams Character Streams 用來處理 16 位元資料,如:字元資料(unicode) Reader 和 Writer 是所有 character streams 的 abstract superclasses
Overview of I/O Streams Byte Streams 用來處理 8 位元資料,如:執行檔、圖檔和聲音檔 InputStream 和 OutputStream 是所有 byte streams 的 abstract superclasses
Overview of I/O Streams Understanding the IO Superclasses Reader 與 InputStream 有非常類似的 API,只是處理的資料型態不同,如: Reader 中的 methods int read() int read(char cbuf[]) int read(char cbuf[], int offset, int length) InputStream 中的 methods int read(byte buf[]) int read(byte buf[], int offset, int length)
Overview of I/O Streams Writer 與 OutputStream Writer 中的 methods int write(int c) int write(char cbuf[]) int write(char cbuf[], int offset, int length) OutputStream 中的 methods int write(byte buf[]) int write(byte buf[], int offset, int length) 所以的 Streams 都一樣,在物件建立之後,就會自動開啟,而呼叫 close() 就可以關閉資料流
Using the Streams I/O 類型 資料流類別 說明 檔案(File) 功能在於存取檔案或檔案系統的內容 管線(Pipe) FileReader FileWriter FileInputStream FileOutputStream 功能在於存取檔案或檔案系統的內容 管線(Pipe) PipedReader PipedWriter PipedInputStream PipedOutputStream 將某個程式(或 Thread)的輸出導入另一個程式的輸入 串接(Concatenate) 未提供 SequenceInputStream 將多個 input stream 串接到同一個 input stream
Using the Streams I/O 類型 資料流類別 說明 緩衝(Buffer) 讀寫時為資料緩衝區,可以減少存取原始資料的次數 BufferedReader BufferedWriter BufferedInputStream BufferedOutputStream 讀寫時為資料緩衝區,可以減少存取原始資料的次數 過濾(Filter) FilterReader FilterWriter FilterInputStream FilterOutputStream 這幾個類別都是 abstract class,定義了過濾讀寫資料的介面 物件序列化 (Object Serialization) 未提供 ObjectInputStream ObjectOutputStream 用來做物件序列化(Object Serialization)的動作
File 檔案與目錄之操作 在 Java 中建立 File 物件,就可以取得檔案的或資料夾的相關訊息,如:File f = new File(String); File() 建構元參數是檔案或是資料夾的路徑 例如: import java.io.*; File f = new File(“haha.txt”);
File File 的相關方法 public boolean isDirectory() public boolean isFile() 傳回目前 java.io.File 內所包含的這個名稱是否為一個資料夾 public boolean isFile() 傳回目前 java.io.File 內的名稱是否為一個檔案 public String[] list() 傳回目前資料夾內所包含的資料夾名稱及檔案名稱
檔案清單範例 Example: Dir.java import java.io.*; public class Dir { public static void main(String[] args) { String[] filenames; File f = new File(args[0]); if ( f.isDirectory() ) { filenames = f.list(); for(int i=0; i< filenames.length; i++) System.out.println(filenames[i]); } else System.out.println(f + "is not a directory");
顯示檔案資訊 相關方法 public boolean exists(): 檢查檔案是否存在 public boolean canRead(): 是否可讀 public boolean canWrite(): 是否可寫 public getName(): 傳回該檔案的名稱 public long length(): 傳回檔案大小 public long lastModified(): 傳回檔案修改時間 public getPath(): 取得該檔案所屬的資料夾名稱 public getParent(): 取得該檔案所屬的父資料夾名稱 public getAbsoluteFile(): 傳回該檔案的絕對路徑
顯示檔案資訊範例 Exmaple: FileInfo.java File f = new File(args[0]); if ( f.exists() ) { if ( f.isFile() ) System.out.print("File: "); else if(f.isDirectory()) System.out.print("Directory: "); System.out.println(f.getAbsoluteFile()); System.out.println("Length: " + f.length()); System.out.println("Readable: " + f.canRead()); System.out.println("Writable: " + f.canWrite()); } else System.out.println(f + " does not exist!");
更改檔案名稱 相關方法 public boolean renameTo(File dest) 功能:更改檔名 傳回值:true 成功,false 失敗
更改檔案名稱範例 Example: RenameFile.java File fs = new File(args[0]); File fd = new File(args[1]); if ( fs.exists() ) { if ( !fd.exists() ) { if (fs.renameTo(fd)) { System.out.println(fs.getName() + " --> " + fd.getName()); System.out.println("1 file(s) has been renamed!"); } else System.out.println("Target name already exists!"); System.out.println("Wrong source name!");
File 相關方法 刪除檔案 public boolean delete() 功能:刪除檔案 傳回值:true 刪除成功;false 失敗 建立資料夾 boolean mkdir() boolean mkdirs() 建立資料夾,成功傳回 true;失敗 false
建立資料夾範例 Example: MakeDir.java import java.io.*; public class MakeDir { public static void main(String[] args) { File dir = new File(args[0]); if ( !dir.exists() ) { boolean success = dir.mkdir(); // boolean success = dir.mkdirs(); System.out.println("Create "+dir+" is successed!!"); } else System.out.println(dir + " is already exist!!!");
FileReader / FileWriter 可利用 File 物件來當作 FileReader / FileWriter 的建構元參數 如: File inputFile = new File(“test.txt”); FileReader in = new FileReader(inputFile); 建立 FileReader 的物件之後可用 read() 來讀入 16-bit 字元 建立 FileWriter 的物件之後可用 write() 來寫出 16-bit 字元
FileReader / FileWriter Example: Copy.java … File inputFile = new File("farrago.txt"); File outputFile = new File("outagain.txt"); FileReader in = new FileReader(inputFile); FileWriter out = new FileWriter(outputFile); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.close();
Wrap Streams Wrap Streams – 包裹 Stream 以一種 stream 類別包裹另一種 stream 類別的情形,這樣可以將各種不同資料流中好用的 methods 合併使用 在讀取資料時,可以利用 BufferedReader 將來源包裹(wrap)起來,如此一來便可以使用呼叫 BufferedReader 中提供的 readLine() 來讀入一整行的資料
Java讀寫檔案 BufferedReader 和 BufferedWriter 程式做 IO 動作時,使用 8K 左右大小之 Buffer 可得到最佳效能 BufferedReader 和 BufferedWriter 已經內建 Buffer 幫你做實際 IO 傳輸 buffer 處理 示範程式 使用 BufferedReader 讀出檔案內容
示範程式 Example: BufReaderDemo.java String file = "farrago.txt"; File name = new File(file); if ( name.exists() ) { BufferedReader input = new BufferedReader( new FileReader(name)); String str; while ( (str = input.readLine()) != null ) // 判別是否讀到檔尾 System.out.println(str); input.close(); } else System.out.println("File [" + name + "] doesn't exist."); FileIO.java and TestFileIO.java
BufferedReader使用法 Constructor: BufferedReader(Reader in); Reader 是一個 abstract 類別,不能被 new 出來,只能被繼承使用 查API可知道 Reader 被 FileReader 繼承。 而 FileReader 有一 Constructor 為 FileReader(String filename) 因此我們可以 new 一個 FileReader,當作參數傳入BufferedReader 之 Constructor
BufferedWriter使用法 Constructor: BufferedWriter(Writer out); Writer 是一個 abstract 類別,不能被 new 出來,只能被繼承使用 查 API 可知到 Writer 被 FileWriter 繼承。 而 FileWriter 有一 Constructor 為 FileWriter(String filename) 因此我們可以 new 一個 FileWriter,當作參數傳入BufferedWriter 之 Constructor
為何Java如此大費周章 使用BufferedReader為何還要先產生FileReader,再指向檔案名稱? BufferedReader br = new BufferedReader(new FileReader(“Test.txt”)); 因為 BufferedReader 和 BufferedWriter 不只可以做檔案處理之功能 只需置換 BufferedReader 之建構子,即可使用相同的API做出讀取鍵盤輸入和網路傳輸等功能 請注意 BufferedReader 和 BufferedWriter 的建構子為 Reader 和 Writer 之類別
InputStream / OutputStream 在 Java 中,相對應於 Reader / Writer,用來讀取和寫入位元或位元陣列 提供的方法: int read() -- 讀取一個位元 int read(byte[]) – 讀取位元陣列,當資料結尾時,傳回 -1 int read(byte[], int, int) void write(int) – 寫入整數 void write(byte[]) – 寫入 1 個位元組陣列 void write(byte[], int, int)
InputStream / OutputStream 對於位元的檔案 IO,Java 程式是開啟 FileOutputStream 和 FileInputStream 串流,如: FileOutputStream output = new FileOutputStream(file); … FileInputStream input = new FileInputStream(name); 參數可以是檔案路徑字串,或是 File 物件
InputStream / OutputStream Example: InOutStreamDemo.java FileInputStream input = new FileInputStream(name); FileOutputStream output = new FileOutputStream("clone.jpg"); // 當讀取的檔案不是文字檔的話,用 FileReader 來讀取會有問題 //FileReader input = new FileReader(name); //FileWriter output = new FileWriter("clone.jpg"); int b; while ( (b = input.read()) != -1 ) { output.write(b); System.out.println(b); // 顯示出一個 pixel 的數值 } output.close(); input.close();
Your Turn 建立一個 Java 程式從命令參數列輸入個數和最大值 maxValue,然後使用亂數產生 0 ~ maxValue 的整數值,以每個整數間隔一個 Tab,每一列 5 個的方式將整數以字元方式寫入文字檔案中。 產生亂數的方式:Math.random() 記得處理例外 試利用 BufferedReader 中的 readLine() 來將剛剛產生的檔案內容讀出 See FileIO.java