程序猿们为了配合市场工作临时上了一个活动,算是一个小功能,需求如下:
- 有三个页面,参与活动用户信息录入页面;活动页面,有问卷调查;活动参与结果页面;
- 要求活动信息留存,用户提交后,立即邮件提醒运营。
本来对应方案: 信息读取录入到excel文件中。
问题:
由于对并发读写excel没有预判和多线程知识短板,造成该方案堵塞, 话不多说上代码。
// 写文件操作
public ResponseEntity saveInfo(@RequestParam(value = "mobileNum") String mobileNum,
@RequestParam(value = "url") String url,
@RequestParam(value = "answer") String answer) {
ActivityResponseDTO response = new ActivityResponseDTO();
File file = getActivityXls();
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file);
POIFSFileSystem ps = new POIFSFileSystem(fis);
HSSFWorkbook wb = new HSSFWorkbook(ps);
HSSFSheet sheet = wb.getSheetAt(0);
fos = new FileOutputStream(file);
HSSFRow row = sheet.createRow(sheet.getLastRowNum() + 1);
row.createCell(0).setCellValue(sheet.getLastRowNum() + 1);
row.createCell(1).setCellValue(mobileNum);
row.createCell(2).setCellValue(url);
String code = NumberUtil.format(sheet.getLastRowNum() + 1, 3);
row.createCell(3).setCellValue(code);
response.setCode(code);
String[] answers = answer.split(",");
for (int i=4; i< 11; i++) {
row.createCell(i).setCellValue(answers[i-4]);
}
fos.flush();
wb.write(fos);
} catch (Exception e) {
LOGGER.error("", e);
} finally {
try {
if (null != fos) {
fos.close();
}
if (null != fis) {
fis.close();
}
} catch (IOException e) {
LOGGER.error("", e);
}
}
LOGGER.info("===========================save response: "+response.toString());
noticev4Biz.sendCardMail(mobileNum, url);
return createResponse(response);
}
解决
从代码可以看出,读逻辑是有同步的,但也只是对单用户操作的同步,写操作没有同步保护。 谁是竞争的资源?是excel文件。所以要保证所有的线程对该文件实现读写同步。可以实现一个该文件的饱汉单例,对该单例进行写同步。
// 写文件操作(同步之后的代码)
public ResponseEntity saveInfo(@RequestParam(value = "mobileNum") String mobileNum,
@RequestParam(value = "url") String url,
@RequestParam(value = "answer") String answer) {
ActivityResponseDTO response = new ActivityResponseDTO();
response.setCode(ExcelSingleton.write2File(mobileNum, url, answer));
LOGGER.info("===========================save response: "+response.toString());
noticev4Biz.sendCardMail(mobileNum, url);
return createResponse(response);
}
/**
* 对固定Excel文件写操作同步
*/
public class ExcelSingleton {
private static final String EXCEL_FILE_PATH = "/tmp/activity/activity.xls";
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelSingleton.class);
private static File excel = new File(EXCEL_FILE_PATH);
public static synchronized String write2File(String mobileNum, String url, String answer) {
LOGGER.info("当前线程{}, 开始写入.", Thread.currentThread().getName());
try (
FileInputStream fis = new FileInputStream(excel);
) {
POIFSFileSystem ps = new POIFSFileSystem(fis);
HSSFWorkbook wb = new HSSFWorkbook(ps);
HSSFSheet sheet = wb.getSheetAt(0);
FileOutputStream fos = new FileOutputStream(excel);
HSSFRow row = sheet.createRow(sheet.getLastRowNum() + 1);
row.createCell(0).setCellValue(sheet.getLastRowNum() + 1);
row.createCell(1).setCellValue(mobileNum);
row.createCell(2).setCellValue(url);
String code = NumberUtil.format(sheet.getLastRowNum() + 1, 3);
row.createCell(3).setCellValue(code);
String[] answers = answer.split(",");
for (int i = 4; i < 11; i++) {
row.createCell(i).setCellValue(answers[i - 4]);
}
wb.write(fos);
fos.close();
fis.close();
LOGGER.info("当前线程{}, 写入完毕.", Thread.currentThread().getName());
return code;
} catch (Exception e) {
LOGGER.error("写入文件失败:", e);
LOGGER.info("当前线程{}, 写入完毕.", Thread.currentThread().getName());
return null;
}
}
/**
* 没有该Excel文件时,先运行该方法
*/
public static File getActivityXls() {
File targetFile = new File(EXCEL_FILE_PATH);
if (targetFile.exists()) {
return targetFile;
}
HSSFWorkbook book = new HSSFWorkbook();
HSSFSheet sheet = book.createSheet("清凉一夏");
sheet.setDefaultColumnWidth(50);
HSSFRow firstRow = sheet.createRow(0);
CellStyle titleStyle = book.createCellStyle();
titleStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
titleStyle.setFillPattern(CellStyle.BORDER_THICK);
titleStyle.setBorderBottom(HSSFCellStyle.BORDER_THICK);
titleStyle.setBorderTop(HSSFCellStyle.BORDER_THICK);
titleStyle.setBorderLeft(HSSFCellStyle.BORDER_THICK);
titleStyle.setBorderRight(HSSFCellStyle.BORDER_THICK);
HSSFFont titleFont = book.createFont();
titleFont.setColor(IndexedColors.BLACK.getIndex());
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
titleStyle.setFont(titleFont);
firstRow.setHeightInPoints(3 * 20);
HSSFCell cell = firstRow.createCell(0);
cell.setCellStyle(titleStyle);
cell.setCellValue("序号");
cell = firstRow.createCell(1);
cell.setCellStyle(titleStyle);
cell.setCellValue("手机号");
cell = firstRow.createCell(2);
cell.setCellStyle(titleStyle);
cell.setCellValue("名片");
cell = firstRow.createCell(3);
cell.setCellStyle(titleStyle);
cell.setCellValue("抽奖码");
cell = firstRow.createCell(4);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目1");
cell = firstRow.createCell(5);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目2");
cell = firstRow.createCell(6);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目3");
cell = firstRow.createCell(7);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目4");
cell = firstRow.createCell(8);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目5");
cell = firstRow.createCell(9);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目6");
cell = firstRow.createCell(10);
cell.setCellStyle(titleStyle);
cell.setCellValue("题目7");
ByteArrayOutputStream os = new ByteArrayOutputStream();
targetFile.getParentFile().mkdirs();
try {
book.write(os);
byte[] ba = os.toByteArray();
FileUtils.writeByteArrayToFile(targetFile, ba);
} catch (IOException e) {
LOGGER.error("", e);
} finally {
try {
if (null != os) {
os.close();
}
} catch (IOException e) {
LOGGER.error("", e);
}
}
return targetFile;
}
public static void main(String[] args) {
// getActivityXls(); // 运行下面代码时,注释掉该代码
new Thread(() -> {
System.out.println(write2File("1", "url1", "A,B,C,D,E,F,G"));
}, "One").start();
new Thread(() -> {
System.out.println(write2File("2", "url1", "A,B,C,D,E,F,G"));
}, "Two").start();
new Thread(() -> {
readFile();
}, "readOne").start();
new Thread(() -> {
System.out.println(write2File("3", "url1", "A,B,C,D,E,F,G"));
}, "Three").start();
new Thread(() -> {
System.out.println(write2File("4", "url1", "A,B,C,D,E,F,G"));
}, "Four").start();
new Thread(() -> {
readFile();
}, "readTwo").start();
new Thread(() -> {
System.out.println(write2File("5", "url1", "A,B,C,D,E,F,G"));
}, "Five").start();
new Thread(() -> {
System.out.println(write2File("6", "url1", "A,B,C,D,E,F,G"));
}, "Six").start();
}
}