題目描述
一個檔案中有10000個數,用Java實作一個多線程程式将這個10000個數輸出到5個不用檔案中(不要求輸出到每個檔案中的數量相同)。要求啟動10個線程,兩兩一組,分為5組。每組兩個線程分别将檔案中的奇數和偶數輸出到該組對應的一個檔案中,需要偶數線程每列印10個偶數以後,就将奇數線程列印10個奇數,如此交替進行。同時需要記錄輸出進度,每完成1000個數就在控制台中列印目前完成數量,并在所有線程結束後,在控制台列印”Done”.
思路
首先,抓住題目的重點,就是如何讓兩個線程(奇偶)交替幹活。我們學過的線程排程的方法:(1)sleep/interrupt,為了實作兩個奇偶線程交替寫入,我們必須給兩個線程使用同一個對象鎖,但是線程sleep之後,隻是讓出CPU資源,但并不釋放對象鎖,也就是在一個線程休眠之後,另一個線程還是無法寫入。如果不加鎖,線程休眠無法準確定證線程的調用順序。(2)設定線程優先級,與線程休眠類似,線程的優先級仍然無法保障線程的執行次序。隻不過,優先級高的線程擷取CPU資源的機率較大,優先級低的并非沒機會執行。 (3)讓步。線程的讓步含義就是使目前運作着線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀态回到可運作狀态。可能讓步完,自己線程接着進入運作狀态。(4)合并。線程的合并的含義就是将幾個并行線程的線程合并為一個單線程執行,應用場景是當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。 也就是說如果兩個線程互相調用對方的join方法來實作交替是什麼結果呢?看測試結果:
//偶數線程
public void run() {
while (step <= ) {
synchronized (readNum) {
try {
readEvenNum(br, bw);
step++;
System.out.println("偶數讀了10個,讓給奇數。。。。"+step);
oddThread.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//奇數線程
public void run() {
while (step <= ) {
synchronized (readNum) {
try {
readOddNum(br, bw);
step++;
System.out.println("奇數讀了10個,讓給偶數。。。。"+step);
evenThread.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
運作結果是:

結果是某個線程運作一次之後就死鎖了。
總結上面錯誤的思路之後,就是應了那句老話:不管程式員怎麼編寫排程,隻能最大限度的影響線程執行的次序,而不能做到精準控制。
個人解題思路
到這裡,思路差不多就出來了,使用鎖+wait/notify機制。
syn(obj){
obj.read();
obj.notify();
obj.wait();
}
(1)為了保證兩個線程能交替幹活,給兩個線程加上同一個對象鎖,這樣即使一個線程寫資料寫到一半退出CPU,但是我還是繼續持有該對象鎖,另一線程也不能執行寫入操作,進而保證了可以交替寫入。(2)wait/notify可以保證一個線程在完成寫入操作之後,喚醒該對象鎖上等待的線程(也就是對面那個線程),同時讓自己線程在 該對象螢幕上進入等待狀态,知道對面線程将其喚醒。
注:wait和sleep差別是,兩者都會暫停目前線程,釋放CPU控制權,但是wait還會釋放對象鎖的控制;還要注意的是:notify()調用之後,并不會立即釋放對象鎖,而是在相應的synchronized(){}語句執行完後,自動釋放鎖,JVM會在wait對象鎖的線程中随機選取一個線程,賦予其對象鎖,喚醒線程,繼續執行。
代碼示範(為了友善調試,将題目的數量級降到原來的1/10):
package wangyi;
import java.io.*;
public class ReadNum {
public static void main(String[] args) throws Exception {
File file = new File("D:\\readNum\\num.txt");
File file1 = new File("D:\\readNum\\out1\\out1.txt");
ReadNum readNum = new ReadNum();
EvenThread evenThread = new EvenThread(file, file1, readNum);
OddThread oddThread = new OddThread(file, file1, readNum);
evenThread.start();
oddThread.start();
}
}
class EvenThread extends Thread {
FileReader fis;
FileWriter fos;
BufferedReader br;
BufferedWriter bw;
ReadNum readNum;
int step = ;
public EvenThread(File file, File file1, ReadNum readNum) throws Exception {
this.fis = new FileReader(file);
this.fos = new FileWriter(file1,true);
this.br = new BufferedReader(fis);
this.bw = new BufferedWriter(fos);
this.readNum = readNum;
}
public void run() {
while (step <= ) {
synchronized (readNum) {
try {
readEvenNum(br, bw);
step++;
readNum.notify();
readNum.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void readEvenNum(BufferedReader br, BufferedWriter bw)
throws IOException {
int count = ;
while (count < ) {
String str = br.readLine();
if(str==null){
System.out.print(str+" ");
}else{
if (Integer.parseInt(str) % == ) {
bw.write(str);
bw.write(" ");
bw.flush();
count++;
}
}
}
}
}
class OddThread extends Thread {
FileReader fis;
FileWriter fos;
BufferedReader br;
BufferedWriter bw;
ReadNum readNum;
int step = ;
public OddThread(File file, File file1, ReadNum readNum) throws Exception {
this.fis = new FileReader(file);
this.fos = new FileWriter(file1,true);
this.br = new BufferedReader(fis);
this.bw = new BufferedWriter(fos);
this.readNum = readNum;
}
public void run() {
while (step <= ) {
synchronized (readNum) {
try {
readOddNum(br, bw);
step++;
if (step % == ) {
System.out.println("已完成列印數量:" + step * );
if (step == ) {
System.out.println("Done!");
}
}
readNum.notify();
readNum.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void readOddNum(BufferedReader br, BufferedWriter bw)
throws IOException {
int count = ;
while (count < ) {
String str = br.readLine();
if(str==null){
System.out.print(str+" ");
}else{
if (Integer.parseInt(str) % == ) {
bw.write(str);
bw.write(" ");
bw.flush();
count++;
}
}
}
}
}
至于題目要求的五組線程,隻需要使用五個不同的對象鎖即可。