案例一:实现对共享数据的并发操作
案例一:实现对共享数据的并发操作
项目需求:在学生管理系统中,学生是可以实现在线选课的,课程的名额是一个共享资源,在并发操作下,如果不做任何的安全操作的话,就会很危险。
我的处理方式:
1、使用Synchronized
2、使用ReenTrackLock
3、使用Volatile关键字(属于强行使用了)
4、使用RabbitMQ消息队列实现并发操作
个人文档如下:
测试对共享资源并发竞争
需求:1000个学生对40个课程名额进行争抢,实现在高并发的情况下,实现压测。
方式一:使用非安全的方式实现:
前端设计:<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>争抢课程</title> </head> <body> <!--<a th:href="@{/student/getCourse/{i}(i = 1)}">争抢课程</a>--> <script th:src="@{/jquery-1.9.1.js}"></script> <button onclick="ceshi(1000)">争抢课程</button> </body> <script type="text/javascript"> function ceshi(number) { for (var i = 1; i <= number; i++) { $.get("/student/getCourse/" + i,function (result) { }) } } </script> </html>
Controller层:
@Controller @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; /** * 获得课程剩余数量 * 现在的代码就是有问题的,可能会出现线程安全问题的 * @return */ @RequestMapping("/getCourse/{i}") @ResponseBody public String getCourse(@PathVariable String i) { System.out.println("当前进入的消费者ID:" + i); // 获取课程剩余数量 int courseCount = studentService.getCourseCountService(); // 判断课程数量的大小 if (courseCount > 0) { // 课程数量减一 int decreaseCourse = studentService.decreaseCourseCountService(); if (decreaseCourse == 1) { System.out.println("消费者ID" + i + " 争夺成功!"); } } return null; } }Service层:
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; /** * 获得课程剩余数量 * @return */ @Override public int getCourseCountService() { return studentMapper.getCourseCountMapper(); } /** * 课程剩余数量减一 * @return */ @Override public int decreaseCourseCountService() { return studentMapper.decreaseCourseCountMapper(); } }Mapper层:不做具体的展示。
方式二:使用Synchronized关键字实现同步机制
全部代码相同,只需要添加synchronized关键字在controller方法上或者service实现类方法上。
@Controller @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; /** * 获得课程剩余数量 * 现在的代码就是有问题的,可能会出现线程安全问题的 * @return */ @RequestMapping("/getCourse/{i}") @ResponseBody public synchronized String getCourse(@PathVariable String i) { System.out.println("当前进入的消费者ID:" + i); // 获取课程剩余数量 int courseCount = studentService.getCourseCountService(); // 判断课程数量的大小 if (courseCount > 0) { // 课程数量减一 int decreaseCourse = studentService.decreaseCourseCountService(); if (decreaseCourse == 1) { System.out.println("消费者ID" + i + " 争夺成功!"); } } return null; } }
方式三:使用Volatile关键字实现(使用它的线程可见的特点实现)
方式四:使用ReennTractLock锁实现数据的同步
使用对象锁之前是需要实例化一个对象锁。
@Controller @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; /** * 实例化一个ReenTrackLock锁对象 */ private ReentrantLock reentrantLock = new ReentrantLock(); /** * 获得课程剩余数量 * 现在的代码就是有问题的,可能会出现线程安全问题的 * @return */ @RequestMapping("/getCourse/{i}") @ResponseBody public String getCourse(@PathVariable String i) { try { // 实现加锁 reentrantLock.lock(); System.out.println("当前进入的消费者ID:" + i); // 获取课程剩余数量 int courseCount = studentService.getCourseCountService(); // 判断课程数量的大小 if (courseCount > 0) { // 课程数量减一 int decreaseCourse = studentService.decreaseCourseCountService(); if (decreaseCourse == 1) { System.out.println("消费者ID" + i + " 争夺成功!"); } } } catch (Exception e) { e.printStackTrace(); System.out.println("出现异常"); } finally { // 实现放锁 reentrantLock.unlock(); } return null; } }
方法五:使用RabbitMQ消息队列实现数据的同步机制
在使用RabbitMQ的技术框架中,想要使用消息队列实现并发消息的处理,是完全可以实现的。在架构设计时,根据高内聚的设计原则,将消息的发送方Producer和消息的接收方Consumer分别放在不同的模块中,形成两个模块:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
rabbitmq_producer模块
RabbitMQ配置类:@Configuration public class RabbitConfig { private final String EXCHANGE_NAME = "COURSE_EXCHANGE"; // 交换器的名称 private final String QUEUE_NAME = "COURSE_QUEUE"; // 队列的名称 // 配置交换器 @Bean("EXCHANGE_NAME") public Exchange getExchange() { return new DirectExchange(EXCHANGE_NAME); } // 配置队列 @Bean("QUEUE_NAME") public Queue getQueue() { return new Queue(QUEUE_NAME); } // 绑定队列到交换器上 @Bean public Binding binding(@Qualifier("EXCHANGE_NAME") Exchange exchange, @Qualifier("QUEUE_NAME") Queue queue) { return BindingBuilder .bind(queue) .to(exchange) .with("course") .noargs(); } }Controller层接口:
@Controller @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; @Autowired private RabbitTemplate rabbitTemplate; @RequestMapping("/getCourse/{i}") @ResponseBody public String getCourse(@PathVariable String i) { rabbitTemplate.convertAndSend("COURSE_EXCHANGE", "course", i); return null; } }
rabbitmq_consumer模块
Controller层:@Component public class StudentRabbitConsumer { @Autowired private StudentService studentService; /** * 实现消息的监听 * 对队列中的每一个消息都进行接收和处理。 * @param message */ @RabbitListener(queues = "COURSE_QUEUE") public void ListenerConsumer(String message) { try { System.out.println("当前进入的消费者ID:" + message); // 获取课程剩余数量 int courseCount = studentService.getCourseCountService(); // 判断课程数量的大小 if (courseCount > 0) { // 课程数量减一 int decreaseCourse = studentService.decreaseCourseCountService(); if (decreaseCourse == 1) { System.out.println("消费者ID" + message + " 争夺成功!"); } } } catch (Exception e) { e.printStackTrace(); System.out.println("出现异常"); } } }
Serviec层:
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; /** * 获得课程剩余数量 * * @return */ @Override public int getCourseCountService() { return studentMapper.getCourseCountMapper(); } /** * 课程剩余数量减一 * * @return */ @Override public int decreaseCourseCountService() { return studentMapper.decreaseCourseCountMapper(); } }
@Component public class StudentRabbitConsumer { @Autowired private StudentService studentService; //记录课程剩余的数量 private static Integer COURSE_NUMBER; //函数执行的次数 private static Integer Integer_Flag = 1; /** * 实现消息的监听 * 对队列中的每一个消息都进行接收和处理。 * @param message */ @RabbitListener(queues = "COURSE_QUEUE") public void ListenerConsumer(String message) { if (Integer_Flag == 1) { COURSE_NUMBER = studentService.getCourseCountService(); Integer_Flag = 0; } try { System.out.println("当前进入的消费者ID:" + message); // 判断课程数量的大小 if (COURSE_NUMBER > 0) { // 课程数量减一 COURSE_NUMBER--; System.out.println("消费者ID" + message + " 争夺成功!"); } } catch (Exception e) { e.printStackTrace(); System.out.println("出现异常"); } } }