生命游戏并行化实现(Java)

目录

前言

一、串行实现读取测试样例与输出结果

1.代码展示

2.详细解释

1)读取文件

2)将最终棋盘输出到文件

3)输出文件与预期结果对比

3.输出结果

二、并行化

1.代码解释

2.输出结果

总结

前言

        前文链接:生命游戏串行代码实现(Java)

        本文将对之前的串行生命游戏进行改造,实现并行化,并通过输出运行时间来检验其之间的

效率。

        在此采用的方法是程序读取给定的测试样例作为初始棋盘,随后根据样例要求进化特定代

数,输出结果文件,并和测试用例的预期输出文件进行比对,输出是否一致以及运行时间。

一、串行实现读取测试样例与输出结果

1.代码展示

import java.io.*;
import java.util.Scanner;

public class gameOfLife_1 {
    private int[][] board; // 当前棋盘
    private int[][] nextBoard; // 下一代棋盘
    private int size; // 棋盘大小

    public gameOfLife_1(int size) {
        this.size = size;
        board = new int[size][size];
        nextBoard = new int[size][size];
    }

    // 从文件读取初始棋盘
    private void initializeBoardFromFile(String filename) {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            StringBuilder boardBuilder = new StringBuilder();
            // 读取每一行,构建棋盘内容
            String line;
            while ((line = br.readLine()) != null) {
                boardBuilder.append(line);
            }
            String boardString = boardBuilder.toString();

            // 根据读取到的字符数动态确定棋盘大小(16个字符则构建4*4棋盘,1000000个字符则构建1000*1000棋盘)
            size = (int) Math.sqrt(boardString.length());
            board = new int[size][size];
            nextBoard = new int[size][size];

            // 填充棋盘
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    board[i][j] = boardString.charAt(i * size + j) - '0'; // 将字符转换为数字
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 计算下一代棋盘
    private void updateBoard() {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                int liveNeighbors = countLiveNeighbors(i, j);
                if (board[i][j] == 1) { // 当前细胞为生
                    nextBoard[i][j] = (liveNeighbors == 2 || liveNeighbors == 3) ? 1 : 0;
                } else { // 当前细胞为死
                    nextBoard[i][j] = (liveNeighbors == 3) ? 1 : 0;
                }
            }
        }

        // 更新棋盘状态
        for (int i = 0; i < size; i++) {
            System.arraycopy(nextBoard[i], 0, board[i], 0, size);
        }
    }

    // 计算某个细胞周围的活细胞数量
    private int countLiveNeighbors(int x, int y) {
        int count = 0;
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {
                int nx = x + i;
                int ny = y + j;
                if(nx >= 0 && nx < size && ny >= 0 && ny < size && (i != 0 || j != 0)){
                    if(board[nx][ny] == 1){
                        count++;
                    }
                }
            }
        }
        return count;
    }

    // 启动生命游戏
    public void run(int generations) {
        for (int generation = 0; generation < generations; generation++) {
            updateBoard();
        }
    }

    // 将当前棋盘输出到文件
    private void outputBoardToFile(String filename) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    bw.write(board[i][j] + "");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 比较输出结果与预期结果
    private boolean compareOutputWithExpected(String expectedFilename) {
        try (BufferedReader expectedReader = new BufferedReader(new FileReader(expectedFilename));
             BufferedReader outputReader = new BufferedReader(new FileReader("test_sample\\output.txt"))) {

            String expectedLine = expectedReader.readLine();
            String outputLine = outputReader.readLine();

            return expectedLine.equals(outputLine); // 直接比较
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入测试文件的序号 (1-6): ");
        int testFileIndex = scanner.nextInt();

        System.out.print("请输入进化代数: ");
        int generations = scanner.nextInt();

        gameOfLife_1 game = new gameOfLife_1(0); // 初始化大小为0
        // 根据用户输入的序号生成文件名test_sample/1.end.txt
        String initFilePath = String.format("test_sample\\%d.init.txt", testFileIndex);
        String expectedFilePath = String.format("test_sample\\%d.end.txt", testFileIndex);


        game.initializeBoardFromFile(initFilePath);

        long multiThreadStartTime = System.nanoTime();
        game.run(generations);

        long multiThreadEndTime = System.nanoTime();
        double multiThreadDuration = (multiThreadEndTime - multiThreadStartTime) / 1_000_000.0; // 转换为毫秒
        System.out.println("多线程执行时间: " + multiThreadDuration + " ms");

        game.outputBoardToFile("test_sample\\output.txt");

        boolean isEqual = game.compareOutputWithExpected(expectedFilePath);
        if (isEqual) {
            System.out.println("输出结果与测试用例一致。");
        } else {
            System.out.println("输出结果与测试用例不一致。");
        }

        scanner.close();
    }
}

2.详细解释

       以上代码主要变化有:1.棋盘的初始化不再是赋予随机值,而是读取特定的文件,根据文件里

的数值来赋予生死状态(1为生,0为死);2.将迭代结束后的棋盘输出到文件;3.输出的结果文件

与预期结果文件进行比对,得到比对结果和运行时间。

1)读取文件

  • BufferRead:通过BufferReader读取文件内容,效率较高,比较适合逐行处理。
  • StringBuilder:使用StringBuilder拼接读取到的每行内容,构建棋盘。
  • 动态棋盘大小:使用Math.sqrt()计算字符数的平方根,确定棋盘大小(棋盘限定为N*N的正方形,使用动态棋盘大小是因为测试样例文件中有16个字符样例,也有1000*1000的样例)
  • 循环填充棋盘:使用双重循环遍历,依次从boardString中读取字符并转为正数,填入数组。
  • 字符转换:boardString.charAt(i * size + j ) - '0'将字符转为对应的整数数值(0/1)

2)将最终棋盘输出到文件

  • BufferWriter:使用BufferWriter写入文件。
  • 写入文件:同样使用双重循环遍历棋盘写入数值。
  • 由于测试样例与输出样例的格式,无需换行。

3)输出文件与预期结果对比

  • BufferReader:同样是使用BufferReader读取输出文件和预期结果文件。
  • 逐行对比:使用while循环和equals()方法来比较两文件的内容。
  • 末尾处理:在完成逐行比对后,检查是否还有剩余行。如果一个文件还有剩余行而另一个已经读完,则也认为不一致。

3.输出结果

二、并行化

1.代码解释

        实现生命游戏并行化本文使用了线程池,因此将棋盘的更新封装成了一个类,用于线程池提交任务。此类代码如下:

public class BoardUpdate implements Runnable{

    private int startRow;
    private int endRow;
    private int[][] board;
    private int[][] nextBoard;
    private int size;

    public BoardUpdate(int startRow, int endRow, int[][] board, int[][] nextBoard, int size) {
        this.startRow = startRow;
        this.endRow = endRow;
        this.board = board;
        this.nextBoard = nextBoard;
        this.size = size;
    }

    //生命游戏规则
    @Override
    public void run(){

        for (int i = startRow; i < endRow; i++) {
            for (int j = 0; j < size; j++) {
                int liveNeighbors = countLiveNeighbors(i, j);
                if (board[i][j] == 1) {
                    nextBoard[i][j] = (liveNeighbors == 2 || liveNeighbors == 3) ? 1 : 0;
                } else {
                    nextBoard[i][j] = (liveNeighbors == 3) ? 1 : 0;
                }
            }
        }
    }

    //计算邻居
    private int countLiveNeighbors(int x, int y) {
        int count = 0;
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {
                int nx = x + i;
                int ny = y + j;
                if(nx >= 0 && nx < size && ny >= 0 && ny < size && (i != 0 || j != 0)){
                    if(board[nx][ny] == 1){
                        count++;
                    }
                }
            }
        }
        return count;
    }
}

        主类代码与串行实现的主类代码差别不大,主要差别体现在updateBoard()方法上,并行化在

此处使用了线程池来实现多线程。numThreads为定义的线程数量。采用了数据分解方法,根据线

程数量,给每个线程分配处理size / numThreads 行。在确保所有提交给线程池的任务完成后,主

线程才继续执行(否则可能会出现主程序在工作线程完成之前就执行的情况)。该方法代码如下:

 private void updateBoard() {
        int numThreads = 8;
        //创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        //每个线程处理的行数
        int rowsPerThread = size / numThreads;

        //循环创建线程
        for (int i = 0; i < numThreads; i++) {
            int startRow = i * rowsPerThread;
            int endRow = (i == numThreads - 1) ? size : (i + 1) * rowsPerThread;
            executor.submit(new BoardUpdate(startRow, endRow, board, nextBoard, size));
        }

        // 等待所有线程完成任务
        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新棋盘状态
        for (int i = 0; i < size; i++) {
            System.arraycopy(nextBoard[i], 0, board[i], 0, size);
        }
    }

        如有需要,以下是并行实现生命游戏的主类完整代码:

import java.io.*;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class gameOfLife_2 {
    private int[][] board; // 当前棋盘
    private int[][] nextBoard; // 下一代棋盘
    private int size; // 棋盘大小

    public gameOfLife_2(int size) {
        this.size = size;
        board = new int[size][size];
        nextBoard = new int[size][size];
    }

    // 从文件读取初始棋盘
    private void initializeBoardFromFile(String filename) {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            StringBuilder boardBuilder = new StringBuilder();
            // 读取每一行,构建棋盘内容
            String line;
            while ((line = br.readLine()) != null) {
                boardBuilder.append(line);
            }
            String boardString = boardBuilder.toString();

            // 根据读取到的字符数动态确定棋盘大小(16个字符则构建4*4棋盘,1000000个字符则构建1000*1000棋盘)
            size = (int) Math.sqrt(boardString.length());
            board = new int[size][size];
            nextBoard = new int[size][size];

            // 填充棋盘
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    board[i][j] = boardString.charAt(i * size + j) - '0'; // 将字符转换为数字
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 计算下一代棋盘
    private void updateBoard() {
        int numThreads = 8;
        //创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        //每个线程处理的行数
        int rowsPerThread = size / numThreads;

        //循环创建线程
        for (int i = 0; i < numThreads; i++) {
            int startRow = i * rowsPerThread;
            int endRow = (i == numThreads - 1) ? size : (i + 1) * rowsPerThread;
            executor.submit(new BoardUpdate(startRow, endRow, board, nextBoard, size));
        }

        // 等待所有线程完成任务
        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 更新棋盘状态
        for (int i = 0; i < size; i++) {
            System.arraycopy(nextBoard[i], 0, board[i], 0, size);
        }
    }

    // 启动生命游戏
    public void run(int generations) {
        for (int generation = 0; generation < generations; generation++) {
            updateBoard();
        }
    }

    // 将当前棋盘输出到文件
    private void outputBoardToFile(String filename) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    bw.write(board[i][j] + "");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 比较输出结果与预期结果
    private boolean compareOutputWithExpected(String expectedFilename) {
        try (BufferedReader expectedReader = new BufferedReader(new FileReader(expectedFilename));
             BufferedReader outputReader = new BufferedReader(new FileReader("test_sample\\output.txt"))) {

            String expectedLine = expectedReader.readLine();
            String outputLine = outputReader.readLine();

            return expectedLine.equals(outputLine); // 直接比较
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("请输入测试文件的序号 (1-6): ");
        int testFileIndex = scanner.nextInt();

        System.out.print("请输入进化代数: ");
        int generations = scanner.nextInt();

        gameOfLife_2 game = new gameOfLife_2(0); // 初始化大小为0
        // 根据用户输入的序号生成文件名test_sample/1.end.txt
        String initFilePath = String.format("test_sample\\%d.init.txt", testFileIndex);
        String expectedFilePath = String.format("test_sample\\%d.end.txt", testFileIndex);


        game.initializeBoardFromFile(initFilePath);

        long multiThreadStartTime = System.nanoTime();

        game.run(generations);

        long multiThreadEndTime = System.nanoTime();
        double multiThreadDuration = (multiThreadEndTime - multiThreadStartTime) / 1_000_000.0; // 转换为毫秒
        System.out.println("多线程执行时间: " + multiThreadDuration + " ms");

        game.outputBoardToFile("test_sample\\output.txt");

        boolean isEqual = game.compareOutputWithExpected(expectedFilePath);
        if (isEqual) {
            System.out.println("输出结果与测试用例一致。");
        } else {
            System.out.println("输出结果与测试用例不一致。");
        }

        scanner.close();
    }
}

2.输出结果

*此处对比单线程生命游戏所需时间更长,因为此时算入了创建线程所需的时间(待优化

可以观察到当数据量上来之后,多线程的优势便体现出来了,运行效率上升明显。

更多测试结果有兴趣的可以自行测试。

总结

        以上就是本文的全部内容,主要将之前的串行生命游戏代码进行改造,在验证了生命游戏规

则算法的正确性的同时,也体现了多线程编程的优势。

        通过使用线程池,我们有效地分配了任务,确保每个线程处理特定行的细胞状态,最大化利

用计算资源。同时,增加了对结果的一致性检查,确保程序输出的正确性和可靠性。

        这种并行化的改造不仅提高了程序的性能,还为以后的扩展和优化提供了基础。希望本文能

为大家提供有关并行计算和生命游戏实现的启示,鼓励更多人探索多线程编程的潜力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值