You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java多线程读取CSV文件:3线程共享Scanner写入ArrayList实现问题

问题

我正在学习Java,这是第一次使用线程。练习要求读取一个CSV文件,额外任务是用3个线程读取该文件,并将信息存入同一个ArrayList<Cliente>中,需要确保每个线程读取唯一的文件行。当前代码未实现多线程逻辑,现附上CSV文件内容及相关Java代码,寻求可行的实现方案。

CSV文件内容

Clienti
nome,cognome,tipoMezzo,tipoParcheggio,dataOperazione,Operazione
Paolo,Rossi,cabrio,coperto,07/12/2022 09:00,0
Chiara,Bianchi,moto,aperto,07/12/2022 09:01,0
Abbondazio,Addolorata,4x4,aperto,07/12/2022 09:10,0
Agamennone,Agatangelo,moto,coperto,07/12/2022 09:11,0
Aldebrando,Barachisio,berlina,coperto,07/12/2022 09:12,0
Aldighiero,Barbaziano,moto,aperto,07/12/2022 10:01,0
Aldobrando,Bardomiano,moto,coperto,07/12/2022 10:05,0
Angilberto,Barsanufio,cabrio,aperto,07/12/2022 10:06,0

原Java代码

CarParkingImpl.java

public class CarParkingImpl implements CarParking {
    ArrayList<Cliente> lista;
    byte coperto;
    byte aperto;

    public CarParkingImpl(){
        coperto=30;
        aperto =50;
        lista = new ArrayList<Cliente>();
    }

//This function must work with 3 operating Threads that read the same .cvs file and are syncronize to read each one a different line and put the information readed in the ArrayList 'lista'

    @Override
    public void carParkingFromCSV(String filePath) throws FileNotFoundException {
        Scanner scan = new Scanner(new File(filePath));
        scan.next();
        scan.next();
        while(scan.hasNext()){
            String[] clientInfo1 = scan.next().split(",");
            String[] clientInfo2 = scan.next().split(",");
            String[] daySplit = clientInfo1[4].split("/");
            String[] timeSplit = clientInfo2[0].split(":");
            Calendar tempCalendar = Calendar.getInstance();
            tempCalendar.set(Integer.parseInt(daySplit[2]),Integer.parseInt(daySplit[1])-1,Integer.parseInt(daySplit[0]),Integer.parseInt(timeSplit[0]),Integer.parseInt(timeSplit[1]));
            Cliente tempClient = new Cliente(clientInfo1[0],clientInfo1[1],clientInfo1[2],clientInfo1[3],tempCalendar,Byte.parseByte(clientInfo2[1]));
            try {
                carParkingClient(tempClient);
            }catch (CarParkingIsFullException e){
                System.err.println(e.getMessage());
            }
        }
        scan.close();
    }
}

Cliente.java

public class Cliente {
    String name;
    String surname;
    String carType;
    String parkReq;
    Calendar dateOperation;
    Byte parkOperation;

    public Cliente(String name, String surname, String carType, String parkReq, Calendar dateOperation, Byte parkOperation) {
        this.name = name;
        this.surname = surname;
        this.carType = carType;
        this.parkReq = parkReq;
        this.dateOperation = dateOperation;
        this.parkOperation = parkOperation;
    }
}

Main.java

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        String path = "Clienti.csv";
        String outPath = "Report.txt";

       CarParkingImpl carParking = new CarParkingImpl();
        try {
            carParking.carParkingFromCSV(path);
            carParking.printReport(outPath);
        }catch (FileNotFoundException e){
            System.err.println(e.getMessage());
        }catch (IOException e){
            System.err.println(e.getMessage());
        }
    }
}
解决方案

要实现3个线程安全读取CSV并写入同一集合,需要解决两个核心问题:避免线程重复读取同一行、保证集合操作的线程安全,以下是具体实现方案:

核心思路

  1. 用BufferedReader替代Scanner读取整行,解决原代码中按空格分割日期的问题;
  2. 同步文件读取逻辑,确保同一时间只有一个线程读取行;
  3. 对集合添加操作做线程安全保护,或直接使用线程安全集合;
  4. 用CountDownLatch等待所有线程完成后再关闭资源。

修改后的代码实现

1. 改造CarParkingImpl类

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class CarParkingImpl implements CarParking {
    List<Cliente> lista;
    byte coperto;
    byte aperto;

    public CarParkingImpl(){
        coperto = 30;
        aperto = 50;
        lista = new ArrayList<>(); // 若不想用同步块,可替换为new CopyOnWriteArrayList<>()
    }

    @Override
    public void carParkingFromCSV(String filePath) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        // 跳过前两行表头
        reader.readLine();
        reader.readLine();

        // 计数器,等待3个线程完成
        CountDownLatch latch = new CountDownLatch(3);

        // 创建并启动3个线程
        for (int i = 0; i < 3; i++) {
            new Thread(new CSVReaderTask(reader, latch, this)).start();
        }

        try {
            latch.await(); // 阻塞主线程,直到所有线程完成
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("线程执行被中断", e);
        } finally {
            reader.close();
        }
    }

    // 同步方法,保证多线程下安全添加Cliente
    public synchronized void addCliente(Cliente cliente) {
        try {
            carParkingClient(cliente);
        } catch (CarParkingIsFullException e) {
            System.err.println(e.getMessage());
        }
    }

    // 原有车位处理逻辑(假设已实现)
    @Override
    public void carParkingClient(Cliente cliente) throws CarParkingIsFullException {
        // 此处添加车位判断逻辑,比如根据parkReq减少coperto/aperto计数
        lista.add(cliente);
    }
}

2. 实现CSVReaderTask线程任务类

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.CountDownLatch;

public class CSVReaderTask implements Runnable {
    private BufferedReader reader;
    private CountDownLatch latch;
    private CarParkingImpl carParking;

    public CSVReaderTask(BufferedReader reader, CountDownLatch latch, CarParkingImpl carParking) {
        this.reader = reader;
        this.latch = latch;
        this.carParking = carParking;
    }

    @Override
    public void run() {
        try {
            String line;
            // 同步读取文件行,避免多个线程读到同一行
            synchronized (reader) {
                while ((line = reader.readLine()) != null) {
                    // 解析CSV行数据
                    String[] clientInfo = line.split(",");
                    String[] dateTime = clientInfo[4].split(" ");
                    String[] dateParts = dateTime[0].split("/");
                    String[] timeParts = dateTime[1].split(":");

                    Calendar calendar = Calendar.getInstance();
                    calendar.set(
                            Integer.parseInt(dateParts[2]),
                            Integer.parseInt(dateParts[1]) - 1, // 月份从0开始
                            Integer.parseInt(dateParts[0]),
                            Integer.parseInt(timeParts[0]),
                            Integer.parseInt(timeParts[1])
                    );

                    Cliente cliente = new Cliente(
                            clientInfo[0],
                            clientInfo[1],
                            clientInfo[2],
                            clientInfo[3],
                            calendar,
                            Byte.parseByte(clientInfo[5])
                    );

                    // 调用同步方法添加到集合
                    carParking.addCliente(cliente);
                }
            }
        } catch (IOException e) {
            System.err.println("读取CSV文件出错: " + e.getMessage());
        } finally {
            latch.countDown(); // 线程完成,计数器减一
        }
    }
}

关键说明

  • 行读取同步:通过synchronized(reader)确保同一时间只有一个线程读取文件行,彻底避免重复读取;
  • 集合操作安全:用synchronized方法addCliente保护对lista的修改,若使用CopyOnWriteArrayList则可省去手动同步;
  • 资源清理:用CountDownLatch等待所有线程完成后再关闭BufferedReader,避免资源泄漏;
  • 解析修复:原代码用Scanner.next()会拆分日期时间,改用readLine()读取整行后正确解析,解决数据拆分错误问题。

内容的提问来源于stack exchange,提问作者Neku Sakuraba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 14:55:15