并发环境下会出现什么问题?

上一篇已经测试过,单个请求是能正常执行并且返回的。但是,系统部署在公网上往往不可能一个人使用,因此必须经过并发测试,不求多规范,至少简单的并发测试也是要进行的。
Apifox图形化界面测试十分简单,还能添加变量。如下所示,简单点,两个线程循环两遍。

修改测试代码,Thread.sleep(1000)模拟测试程序需多耗时一秒。编辑一个自增变量(Apifox文档一使用说明,每次请求id+1)

{"code": "public class Main {\n public static void main(String[] args) throws Exception{ Thread.sleep(1000); System.out.println(\"hello world----自增id {% mock 'increment', 1 %} \");}}"
}

下面给出我的测试结果:

线程1第一轮:
{"code": "public class Main {\n public static void main(String[] args) throws Exception{ Thread.sleep(1000); System.out.println(\"hello world----自增id 3 \");}}"
}
{"error": 0,"reason": "运行成功","stderr": "","stdout": "hello world----自增id 4 \n"
}
线程1第二轮:
{"code": "public class Main {\n public static void main(String[] args) throws Exception{ Thread.sleep(1000); System.out.println(\"hello world----自增id 5 \");}}"
}
{"error": 0,"reason": "运行成功","stderr": "","stdout": "hello world----自增id 6 \n"
}
线程2第一轮:
{"code": "public class Main {\n public static void main(String[] args) throws Exception{ Thread.sleep(1000); System.out.println(\"hello world----自增id 4 \");}}"
}
{"error": 0,"reason": "运行成功","stderr": "","stdout": "hello world----自增id 4 \n"
}
线程2第二轮:
{"code": "public class Main {\n public static void main(String[] args) throws Exception{ Thread.sleep(1000); System.out.println(\"hello world----自增id 6 \");}}"
}
{"error": 0,"reason": "运行成功","stderr": "","stdout": "hello world----自增id 6 \n"
}

可知四次网络请求,分别发送了3,4,5,6。而执行结果为(4,4)(6,6)。此时已经出现问题了。

Spring默认单例(后发现与本次无关)

用的的应该都知道SpringMVC(SpringBoot还是有的MVC)的Controller,默认是单例的,可以进行如下测试:
先修改一下TestController的代码,输出对象且直接返回:

@RequestMapping(value = "/run")public String run(@RequestBody JSONObject json){System.out.println(this+"---"+service);/*Answer answer=service.run(json.getString("code"));if(answer==null)return "{\"error\":\"IO错误\"}";elsereturn JSONObject.toJSONString(answer);*/return "";}

然后重启!!!重启!!!重启!!!再用软件发送HTTP请求查看控制台输出

显而易见,每次网络请求都是同一个Controller对象,同一个对象自动注入的依赖Service必然也是一样的。这也说明了Spring确实默认为单例的。
但是,没有什么关系。。。。。。因为并没有用到这两个对象的成员变量,无线程安全问题。

真正原因

随着代码走到TestService,就可以很容易发现这段代码:

 String DIR="d:/javaTest/";String javaFile=DIR+"Main.java";String javaClass="Main";//编译命令String compileCmd=String.format("javac -encoding utf8 %s -d %s",javaFile,DIR);//运行命令String runningCmd=String.format("java -classpath %s %s", DIR, javaClass);//将代码写入到定义路径下特定的java源文件中

hhh,前面简单实现为例方便,路径以及文件名都是写死的。而编译以及运行都是耗时操作,简单画图说明

id=3的请求到达,写入了磁盘还运行得出结果返回就可能被id=4的请求覆盖掉了java源文件,所以才会出现两个线程都返回4的情况。第二轮同理,5被6覆盖了。

如何解决

  1. 加锁(synchronized)

并发问题最新想到的肯定是锁机制,使得临界资源操作同步化来保证线程安全。使用synchronized即可,更细粒度的锁考虑过了不可行,这里耗时的三步:写源文件、编译读源文件写class、执行读class,可以看做一个事务,都有涉及到两个临界资源的读操作。因此读写锁也优化不了多大。。。
已开10线程五轮进行测试,并未发现问题。

@Service
public class TestService {private Boolean javaLock=true;public Answer run(String code){String DIR="d:/javaTest/";String javaFile=DIR+"Main.java";String javaClass="Main";//编译命令String compileCmd=String.format("javac -encoding utf8 %s -d %s",javaFile,DIR);//运行命令String runningCmd=String.format("java -classpath %s %s", DIR, javaClass);synchronized (javaLock) {//将代码写入到定义路径下特定的java源文件中FileWriter writer;try {File dir = new File(DIR);if (!dir.exists()) {dir.mkdir();}writer = new FileWriter(javaFile);writer.write(code);writer.close();} catch (IOException e) {e.printStackTrace();return null;}//编译源文件为class文件Answer answer = ExecUtil.run(compileCmd, false, true);System.out.print(answer.getStderr());//若编译成功即可开始运行if (answer.getError() == 0) {answer = ExecUtil.run(runningCmd, true, true);if (answer.getError() == 0)answer.setReason(Answer.Success);elseanswer.setReason(Answer.RuntimeError);System.out.print(answer.getStdout());} elseanswer.setReason(Answer.Error);return answer;}}
}
  1. 不同文件名

同步代码(上锁)性能较低,还有其他效率更好的方法。
很容易想到随机给文件命名,但由于java的特性,public class必须和文件名保持一致,故无法随机给文件命名,如果是C/C++就可以随机命名实现。文件名不行?那就改目录呗,只要每个线程操作的不是一个文件就不存在线程安全问题了。我想到的是可以在目录上加一个时间戳。。。

package com.deng.service;import com.deng.bean.Answer;
import com.deng.util.ExecUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;@Service
public class TestService {@Value("${java_file.work_dir}")private String work_dir;@Value("${java_file.compile}")private String compile;@Value("${java_file.running}")private String running;public Answer run(String code){String time="/"+ new Date().getTime()+"/";String DIR=work_dir+time;String javaFile=DIR+"Main.java";String javaClass="Main";//编译命令String compileCmd=String.format(compile,javaFile,DIR);//运行命令String runningCmd=String.format(running, DIR, javaClass);//将代码写入到定义路径下特定的java源文件中FileWriter writer;File dir=new File(DIR);try {dir.mkdir();writer = new FileWriter(javaFile);writer.write(code);writer.close();} catch (IOException e) {e.printStackTrace();return null;}//编译源文件为class文件Answer answer= ExecUtil.run(compileCmd,false,true);System.out.print(answer.getStderr());//若编译成功即可开始运行if(answer.getError()==0) {answer = ExecUtil.run(runningCmd, true, true);if(answer.getError()==0)answer.setReason(Answer.Success);elseanswer.setReason(Answer.RuntimeError);System.out.print(answer.getStdout());}elseanswer.setReason(Answer.Error);//删除两个文件+文件夹,若想要复查代码可以不删,此处需求是在线运行不是判题(虽然保存也是存数据库),故直接删除new Del(javaFile,DIR+javaClass+".class",dir).start();return answer;}class Del extends Thread{private File javaFile;private File classFile;private File dir;Del(String javaFile,String classFile,File dir){this.classFile=new File(classFile);this.javaFile=new File(javaFile);this.dir=dir;}@Overridepublic void run() {javaFile.delete();classFile.delete();dir.delete();}}
}

application.yml

server:servlet:context-path: /port: 8080java_file:work_dir: "d:/javaTest/"compile: "javac -encoding utf8 %s -d %s"running: "java -classpath %s %s"

主要改动就是目录加上了时间戳,配置写死改成了从配置文件读取,执行结束新启动一个线程删除两个文件及文件夹。

下一步

本篇解决线程安全的问题,但是线程数量依旧没有进行限制以及其他问题,但总算解决了一个问题。后续还会补充完善代码…………

java实现代码在线编译器-从零开发(三)Web并发环境下的线程安全相关推荐

  1. java实现代码在线编译器-从零开发(二)简单SpringBoot网络接口demo

    新建SpringBoot项目 其他语言其他框架也差不多,简单入门写个网络接口还是很快的. 配置文件: server:servlet:context-path: /port: 8080 工程目录: Te ...

  2. java 无锁缓存_如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  3. 如何在高并发环境下设计出无锁的数据库操作(Java版本) 转载

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  4. 搜狗输入法在idea打不了汉字_IDEA开发软件在linux环境下使用搜狗输入法无法进行中文输入...

    IDEA开发软件在linux环境下使用搜狗输入法无法进行中文输入 找到bin目录下的idea.sh文件(其他编辑器也是一样如pycharm.sh.clion.sh).使用文本编译器打开,找到 # -- ...

  5. java支付宝支付_Java 高并发环境下的性能优化,揭秘支付宝技术内幕

    前言 高并发经常会发生在有大活跃用户量,用户高聚集的业务场景中,如:秒杀活动,定时领取红包等. 为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适 ...

  6. java springboot VUE 在线学习平台系统开发mysql数据库web结构java编程计算机网页源码maven项目前后端分离

    一.源码特点   springboot VUE 在线学习平台系统是一套完善的完整信息管理类型系统 前后端分离,结合springboot框架和VUE完成本系统,对理解JSP java编程开发语言有帮助系 ...

  7. ROS开发笔记(5)——基于 python 开发 Turtlebot3 Gazebo仿真环境下键盘操控移动机器人(Teleop-bot )

    前文中记录了随机移动机器人的开发过程,本文内容为Turtlebot3 Gazebo仿真环境下Teleop-bot 键盘操控移动机器人,主要包含以下几个部分: 1.键盘驱动(按键驱动发布keys话题) ...

  8. java邮件附件名称乱码_Java邮件开发(三):解决附件名为乱码及显示友好名称

    遗留的问题有以下两个: 1.附件的名称只能为英文,中文乱码 2.友好名称的显示. 我们使用163等邮箱发送邮件时,我们经常可以看到收件人一栏中会是:张益达 这种方式.在上一版本的代码中并没有使用这样的 ...

  9. 全志F1C200s从零开发-虚拟机搭建Ubuntu环境

    我们从零开始开发编译全志F1C200s,在Vmware上搭建Ubuntu环境,作为编译liunx镜像环境. 1.下载安装VMware 自行百度 2.下载安装Ubuntu20.04 参考:虚拟机VMwa ...

最新文章

  1. 谈一谈浏览器解析CSS选择器的过程【前端每日一题-6】
  2. XPath crash course note
  3. oracle 四分位函数,Oracle分析函数四——函数RANK,DENSE_RANK,FIRST,LAST…
  4. 不妨对苹果保持一点宽容
  5. mysql 代替intersect_MySQL不支持INTERSECT和MINUS及其替代方法_MySQL
  6. Matlab之Kalman:用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法
  7. 写给大数据开发初学者的话 | 附教程
  8. 隐藏apache版本号的方法
  9. Ruby中的%表示法
  10. 程序配置amp;amp;ConfigurationManager
  11. 【Qt】Qt之进程间通信(Windows消息)【转】
  12. python默认参数举例_Python中的默认参数实例分析
  13. mysql select命令加速_数据库教程
  14. Android系统信息获取 之四:系统语言信息获取
  15. 六个好用的程序员开发在线工具
  16. java vm 参数及设置(转载)
  17. uView项目前端获取屏幕高度
  18. Gitlab 回滚到某个commit
  19. linux统计文本每列的最大字符,Linux 文本处理,文本工具,查看,分析,统计文本文件,grep,正则表达式...
  20. rails相当于java中的什么_Rails中ERB中的%,%=,%#和 - %有什么区别?

热门文章

  1. 解析超大JSON文件
  2. PE格式详细讲解2 - 系统篇02
  3. php mysql 查询中文乱码_PHP查询MySQL返回中文乱码问题!!!
  4. 在RHEL6.0 X64系统中安装oracle 11g数据库及安装后设置
  5. 2022-1-9 《聪明的投资者》学习笔记-20.作为投资中心思想的“安全边际“--稳健投资的秘密
  6. java常见编程练习hw一(入门、简单题型)
  7. java设置地图,mapXtreme for java 设置地图中点
  8. 【数据库】快速理解脏读、不可重复读、幻读
  9. git clone出现问题 GnuTLS recv error (-54): Error in the pull function
  10. 电子助力方向机控制模块_易力达电子助力控制器模块ECU威旺五菱长安电动助力方向机电脑板...