事情是这样的,现在遇到一个场景,其实也大多数系统都会遇到的一个场景,就是购买机票的案例。 为了简化问题和保护公司信息安全,把事情简化为在数据库中有个数字,我每次要去读取并更新这个数字,每次加1. 初始值设为0.
设计表为
test {
num int(11) default 0;
}
采用ssm框架,controller层写个TestController
@Controller
public class TestController {
@Autowired
TestService testService;
@RequestMapping(value = "addNum")
@ResponseBody
public HttpRespMsg addNum(
HttpServletResponse response) throws Exception {
HttpRespMsg msg = null;
msg = testService.addNum();
return msg;
}
}
TestService定义如下:
public interface TestService {
public HttpRespMsg addNum();
}
@Transactional
@Service(value="testService")
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
@Override
public HttpRespMsg addNum() {
HttpRespMsg msg = new HttpRespMsg();
TestExample tExp = new TestExample();
List<Test> list = testMapper.selectByExample(tExp);
if (list.size() == 0) {
Test t = new Test();
t.setNum(1);
testMapper.updateByExampleSelective(t, tExp);
} else {
int oldNum = list.get(0).getNum();
System.out.println(Thread.currentThread().getName() + "读取到oldNum=="+oldNum);
Test t = new Test();
t.setNum(oldNum+1);
testMapper.updateByExampleSelective(t, tExp);
}
msg.data = testMapper.selectByExample(tExp).get(0).getNum();
return msg;
}
}
很明显,在多线程模式下addNum会出现问题。 我用了JMeter模拟多线程并发访问,得到了验证。 20个请求,在1秒内同时调用addNum接口,结果数据库中的num字段值为6(有时候是7,8,5不确定)。问题出在读取和写入不是一个原子性操作。
那怎么解决呢?
首先想到@Transaction,不是有原子性特征吗?为什么不行呢,原来他的原子性指的是对数据库的操作失败的回滚,就是要么全部执行成功,要么都不成功。 并不能处理并发的问题。又想到了isolation隔离性,有READ_COMMITTED, READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE。具体含义此处不做解释,自行百度。 我尝试的结果是,前面三个都无效,最后一个会导致dead lock的异常产生,不能正常更新数据。 至此放弃这种方式。
我又尝试使用代码级别的锁, synchronized关键字来控制。
修改代码如下:
@Transactional
@Service(value="testService")
public class TestServiceImpl implements TestService {
@Autowired
private TestMapper testMapper;
@Override
public HttpRespMsg addNum() {
HttpRespMsg msg = new HttpRespMsg();
synchronized (TestService.class) {
TestExample tExp = new TestExample();
List<Test> list = testMapper.selectByExample(tExp);
if (list.size() == 0) {
Test t = new Test();
t.setNum(1);
testMapper.updateByExampleSelective(t, tExp);
} else {
int oldNum = list.get(0).getNum();
System.out.println(Thread.currentThread().getName() + "读取到oldNum=="+oldNum);
Test t = new Test();
t.setNum(oldNum+1);
testMapper.updateByExampleSelective(t, tExp);
}
msg.data = testMapper.selectByExample(tExp).get(0).getNum();
}
return msg;
}
}
以为这样是可以的了。赶紧验证下。 重置数据库的num为0,再跑20个同步线程。结果发现num变成了16,有进步,贴近了。但是为什么不是20呢。经过思考和调试,发现线程的锁是起效的,但是在读取的时候,依然会有读取不到最新数据的情况,结合Transaction的特点,一拍脑袋想起来,原来我们的事务是修饰到函数上的,整个函数执行完了才会提交到数据库。 而我们的锁是加载函数里面的代码块上的,范围小于事务。所以同步的代码执行完了,但是数据并没有立马提交。 导致下一个线程读取的时候依旧是老数据。
好吧,那我把synchronized加到addNum上吧。
public synchronized HttpRespMsg addNum(),
接口定义和实现里面都这样改一下,嗯编译没问题。跑一下试试看, opppps. Spring报错了。
修饰符有问题,Spring不允许在service里面的函数前面加这个关键字。只能用public修饰。
最后没办法,加到TestController里的addNum上吧。
@RequestMapping(value = "addNum")
@ResponseBody
public synchronized HttpRespMsg addNum(
HttpServletResponse response) throws Exception {
HttpRespMsg msg = null;
msg = testService.addNum();
return msg;
}
赶紧测试下。
成功!
然而这样会有性能问题,并发时,不能同时执行。是否可以有数据库的存储过程,手动给表加锁等方式解决,有待探索。
分享到:
相关推荐
在IT行业中,多线程并发编程是至关重要的一个领域,特别是在服务器端开发、大数据处理以及高性能计算中。这里提到的“多线程并发学习书籍”集合包含六本关于这一主题的专业书籍,覆盖了2012年至2018年的最新知识。...
这套课程既可以作为从零基础开始...课程的主要内容涉及有JAVA基础课程、JAVA多线程与并发编程、数据库开发基础和进阶、Spring Framework、Spring进阶、Spring MVC框架、Spring boot、Java常用类库、Java异常处理等等
在【标题】"Spring的多线程应用"中,我们关注的是Spring如何支持和管理多线程,这是现代并发编程的一个关键特性。在【描述】中提到的"一个简单的spring的多线程demo",我们可以理解为一个示例项目,旨在帮助开发者...
Spring Boot通过`TaskExecutor`接口提供了一种方便的方式来实现多线程和并发编程。`TaskExecutor`允许我们定义一个线程池,有效地管理和调度线程资源。本文将深入探讨在Spring Boot中使用多线程开发需要注意的关键点...
1. **Java多线程并发**:在Java中,多线程并发是性能优化的关键。它允许程序同时执行多个任务,提高系统的资源利用率。Java提供了多种创建和管理线程的方式,如Thread类、Runnable接口、Executor框架等。理解线程...
此外,Spring的`@Scope`注解可以帮助管理单例bean在多线程环境下的状态,避免数据不一致。 总之,Spring通过提供高级的线程管理和调度工具,简化了在Java应用程序中实现多线程的复杂性。理解并熟练使用这些工具,能...
Java面试题、JVM面试题、多线程面试题、并发编程、设计模式面试题、SpringBoot面试题、SpringCloud面试题、MyBatis面试题、Mysql面试题、VUE面试题、算法面试题、运维面试题。 收集汇总各行业笔试or编程题解题思路 ...
在Spring框架中,多线程队列执行是一个重要的性能优化策略,它可以帮助应用程序更高效地处理并发任务,尤其是在高负载和大数据量的场景下。本文将深入探讨Spring如何实现多线程队列以及其相关的核心概念和技术。 1....
1. **线程安全**:由于多线程环境下可能存在数据竞争,所以在访问共享资源(如数据库连接)时,需要确保线程安全。可以使用`synchronized`关键字或者`Lock`来同步访问。 2. **事务管理**:在多线程环境中,可能需要...
而多线程在现代编程中扮演着关键角色,特别是在处理并发任务、提高系统效率时。 在"spring4+junit4.8 +多线程TheadTool"的场景下,我们可以深入探讨以下几个知识点: 1. **Spring4框架**:Spring4在Spring3的基础...
在 Java 中,多线程编程是非常重要的一种技术,通过使用多线程可以提高程序的并发性和性能。Spring Boot 提供了多种方式来实现多线程编程,其中最简单的方式就是使用 @Async 注解。 使用 @Async 注解 要使用 @...
在SSM(Spring、SpringMVC、MyBatis)框架背景下,如果你已经有所了解,那么这个例子将帮助你进一步理解Spring框架中的多线程处理。 首先,Maven是基于项目对象模型(Project Object Model,POM)的概念,通过XML...
6. **Spring事务管理**:Spring提供了声明式和编程式的事务管理,确保在多线程环境中数据的一致性。 7. **Spring Bean**:Spring容器管理的对象称为Bean,容器通过XML、注解或Java配置来定义Bean的生命周期和装配...
为了实现多线程并发,我们需要创建一个`Runnable`任务,该任务用于执行HTTP GET请求: ```java public class GetTask implements Runnable { private final String url; public GetTask(String url) { this.url ...
这篇学习笔记涵盖了软件工程师在职业发展中需要掌握的关键领域,包括网络、操作系统、设计模式、Java虚拟机(JVM)、多线程与高并发处理、Spring框架以及MySQL数据库。以下是对这些知识点的详细解读: 1. **网络**...
Java 并发编程是指在 Java 语言中编写多线程程序的技术,旨在提高程序的执行效率和响应速度。Java 并发编程手册是 Java 开发者必备的知识储备,以下是 Java 并发编程的关键知识点: 1. Java 内存模型(JMM) Java ...
- **并发编程**:多线程是并发编程的一种方式,它允许多个任务在同一时间执行,从而提高程序的执行效率。在Java中,可以通过`Thread`类或者实现`Runnable`接口来创建线程。 - **线程池**:为了避免频繁地创建和...
Java前后开发面试题,大厂进阶之路,基于JavaGuide、Cyc大佬、牛客...包含计算机网络知识、JavaSE、JVM、Spring、Springboot、SpringCloud、Mybatis、多线程并发、netty、MySQL、MongoDB、Elasticsearch、Redis、HBASE
对于初学者,这是一个很好的学习资源,可以深入理解Spring Boot的自动配置、Druid的数据源管理和Java的多线程编程。同时,对于有经验的开发者,这个项目也可以作为一个基础,进一步扩展到更复杂的数据库同步和分布式...
Java前后开发面试题,大厂进阶之路,基于JavaGuide、Cyc大佬、牛客...包含计算机网络知识、JavaSE、JVM、Spring、Springboot、SpringCloud、Mybatis、多线程并发、netty、MySQL、MongoDB、Elasticsearch、Redis、HBASE