在 java 中使用多线程对文件进行排序

sorting files using multithreading in java

我被分配了一项任务,将给定文件的所有有序内容写入 result.txt。首先,文件名被分成不同的数组列表,其中每个文件包含一个格式为#n/N 的标签,其中 N 是文件总数。例如

英国探险家詹姆斯·克拉克·罗斯率领第一个 到达北极的探险 #001/004

来自文件 1831-06-01.txt

我的代码的问题是分别写了1,4,2,3的顺序。但是,结果必须按 1,2,3,4 的顺序排列。这可能是由于缺乏同步。尽管如此,我仍在努力解决问题。

这是我的代码:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

class PopThread implements Runnable {
    ArrayList<String> fileList; 
    
    public PopThread(ArrayList<String> fileList) {
        this.fileList = fileList;
    }

    @Override
    public void run() {
        //System.out.println("running\n");

        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        long startTime = System.nanoTime();

        System.out.println("fileList: " + fileList);

        ArrayList<String> sortedFileList = sortFiles(fileList);

        File resultFile = new File("result.txt");

        for (String filename : sortedFileList) {
            Writer w1 = new Writer(filename, resultFile);
            Thread t = new Thread(w1);
            t.setPriority(Thread.MAX_PRIORITY);
            t.start();
        }

        long stopTime = System.nanoTime();
        //System.out.println("Total execution time: " + (stopTime - startTime));
    }

    public ArrayList<String> readFiles(String filename) {
        ArrayList<String> list = new ArrayList<String>();

        try {
            File myObj = new File(filename);
            Scanner s = new Scanner(myObj);
            
            while (s.hasNext()) {
                list.add(s.next());
            }
            
            s.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return list;
    }
    
    public int getNumber(String filename) {
        String lastLine = "";
        String sCurrentLine;
        
        int identifier_integer = -1;
        
        try {
            BufferedReader br = new BufferedReader(new FileReader(filename));
            
            while ((sCurrentLine = br.readLine()) != null) {
                lastLine = sCurrentLine;
            }
            
            String identifier_number = lastLine.substring(1,4);
            identifier_integer = Integer.parseInt(identifier_number);

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

        catch (IOException e) {
            e.printStackTrace();
        }
        
        return identifier_integer;
        
    }
    
    public ArrayList<String> sortFiles(ArrayList<String> listFileName) {
        int i = listFileName.size();
        boolean sorted = false;
        
        while ( (i > 1)  &&  (!(sorted)) ) {
            sorted = true;
            
            for (int j = 1; j < i; j++) {
                if ( getNumber(listFileName.get(j-1)) > getNumber(listFileName.get(j)) ) {
                    String temp = listFileName.get(j-1);
                    listFileName.set(j-1, listFileName.get(j));
                    listFileName.set(j, temp);
                    
                    sorted = false;
                }
            }
            i--;
        }
        
        return listFileName;
    }

}

class Writer implements Runnable {
    String filename;
    File resultFile;

    public Writer(String filename, File resultFile) {
        this.filename = filename;
        this.resultFile = resultFile;
    }

    @Override
    public void run() {
        String content;
        content = readFromFile(filename);
        writeToFile(resultFile, content);
    }

    private static void writeToFile(File resultFile, String content) {
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(resultFile, true));
            writer.write(content);
            //writer.write("file content written");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static String readFromFile(String filename) {
        StringBuffer content = new StringBuffer();

        try {
            String text;
            BufferedReader reader = new BufferedReader(new FileReader(filename));

            while ((text = reader.readLine()) != null) {
                content.append(text);
                content.append("\n");
            }

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

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

        return content.toString();
    }

}


public class q4 {
    public static void main(String[] args) {
        ArrayList<String> filesOne = new ArrayList<String>();
        filesOne.add("1831-06-01.txt");
        filesOne.add("2003-08-27.txt");

        ArrayList<String> filesTwo = new ArrayList<String>();
        filesTwo.add("1961-04-12.txt");
        filesTwo.add("1972-12-11.txt");

        PopThread popRunnableOne = new PopThread(filesOne);
        PopThread popRunnableTwo = new PopThread(filesTwo);

        Thread threadOne = new Thread(popRunnableOne);
        Thread threadTwo = new Thread(popRunnableTwo);

        threadOne.start();
        threadTwo.start();
        try {
            threadOne.join();
            threadTwo.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    
}

(注意:class q4 不能更改)

这个作业太糟糕了。你有我的同情。


您的两个线程必须相互通信。每个线程都必须知道 other 线程接下来要输出的文件名是什么。而且,他们将不得不轮流。每个线程需要循环:

  1. 当我的下一个文件的日期小于或等于另一个线程的下一个文件的日期时,输出我的下一个文件,

  2. 告诉其他线程,“轮到你了,”

  3. 如果我没有更多的文件,然后退出(return从run()方法),否则,等待另一个线程告诉我它是我的再转,

  4. 返回步骤 1。

轮流是作业中最糟糕的部分。任何时候你发现自己需要让线程轮流做某事——任何时候你需要让线程按特定顺序做事——这是一个明确的信号,表明 所有 事情应该完成通过一个线程。


线程通信的唯一方式是通过共享变量。你的导师告诉你不要修改 q4 class,这对你造成了 巨大的 伤害。这会阻止您通过其构造函数将任何共享对象传递给您的 PopThread 实现。

您的两个线程可以共享任何变量的唯一其他方法是使变量 static。强迫你使用 static 是作业中第二糟糕的部分。如果你继续学习软件工程,你会了解到 static 是一个 anti-pattern。使用 static 变量的程序是 脆弱的 (即难以修改),并且它们很难测试。

强制你使用静态变量也会让你的线程做额外的工作来弄清楚谁是谁。通常,我会做这样的事情,这样每个线程都会自动知道哪个状态是它自己的,哪个状态属于另一个人:

class SharedState { ... }

class PopThread {
    public PopThread(
        SharedState myState,
        SharedState otherThreadState,
        ArrayList<String> fileList
    ) {
        this.myState = myState;
        this.otherGuyState = otherThreadState;
        this.fileList = fileList;
        ...initialize this.myState...
    }
    ...
}

class q4 {
    public static void main(String[] args) {
        SharedState stateOne = new SharedState();
        SharedState stateTwo = new SharedState();
        PopThread popRunnableOne = new PopThread(stateOne, stateTwo, filesOne);
        PopThread popRunnableTwo = new PopThread(stateTwo, stateOne, filesTwo);
        ...
    }
}

我能想到的使用静态变量的最佳方法是拥有一个包含两个 SharedState 的数组,并让线程使用一个 AtomicInteger 来为自己分配两个数组槽中的一个:

class PopThread {
    static SharedState[] state = new SharedState [2];
    static AtomicInteger nextStateIndex = new AtomicInteger(0);

    public PopThread(
        SharedState myState,
        SharedState otherThreadState,
        ArrayList<String> fileList
    ) {
        myStateIndex = nextStateIndex.getAndIncrement();
        otherGuysStateIndex = myStateIndex ^ 1;
        this.fileList = fileList;
        ...initialize state[myStateIndex]...
    }
    ...
}