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并写入同一集合,需要解决两个核心问题:避免线程重复读取同一行、保证集合操作的线程安全,以下是具体实现方案:
核心思路
- 用
BufferedReader替代Scanner读取整行,解决原代码中按空格分割日期的问题; - 同步文件读取逻辑,确保同一时间只有一个线程读取行;
- 对集合添加操作做线程安全保护,或直接使用线程安全集合;
- 用
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
相关产品推荐
相关产品推荐

