一.问题描述
- 刚开始的时候我是有一个点赞功能需要实现,所以涉及到数据库的读写,并且考虑到并发的问题,所以肯定需要保证数据一致,于是我抛开一切直接用最笨但是最有效(我做的是一个朋友的毕设,所以就无需考虑太多,比如丧失了并发的能力哈哈哈)我直接在controller中加了synchronize同步锁
@ResponseBody
@Transactional
@GetMapping("/up")
public String up(String upid,HttpServletRequest request) {
synchronized (this)
{
System.out.println("开始"+new SimpleDateFormat("yyyy-MM-dd HH🇲🇲ss").format(new Date()));
User loginUser = (User)request.getSession().getAttribute("loginUser");
User userTemp = userMapper.getUserByUid(upid);
System.out.println(request.getHeader("User-Agent")+"取出来了==="+userTemp.getUpcount());
userTemp.setUpcount(userTemp.getUpcount()+1);
userMapper.updateUser(userTemp);
System.out.println(request.getHeader("User-Agent")+"更新完了==="+userTemp.getUpcount());
System.out.println("结束"+new SimpleDateFormat("yyyy-MM-dd HH🇲🇲ss").format(new Date()));
return String.valueOf(userTemp.getUpcount());
}
// }
}
上面的代码是我为了测试方便,改的,所以业务方面都没有,是测试的
- 之后我用httpclient直接开了20个线程不断的发送请求,代码如下(其实同时最多2个左右,但是)
Thread thread01 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i+'次');
uptest(Thread.currentThread().getName()+i);
}
},"A");
Thread thread02 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--第"+i+'次');
uptest(Thread.currentThread().getName()+i);
}
},"B");
thread01.start();
thread02.start();
public static void uptest(String rename)
{
try {
try {
// HttpClient httpclient = HttpClients.custom().build();
HttpClient httpclient = HttpClients.custom().setDefaultCookieStore(STORE).build();
HttpGet httpGet = new HttpGet();
httpGet.setHeader("User-Agent", rename);
httpGet.setHeader("Accept-Encoding", "gzip, deflate, br");
httpGet.setHeader("Accept-Language", "zh-CN,zh;q=0.9");
httpGet.setHeader("accept", "text/plain, */*; q=0.01");
httpGet.setHeader("X-Requested-With","XMLHttpRequest");
httpGet.setURI(new URI(url));
HttpResponse httpresponse = httpclient.execute(httpGet);
response = EntityUtils.toString(httpresponse.getEntity());
System.out.println(Thread.currentThread().getName()+" ===返回数据=== "+response.toString());
}
catch (Exception a)
{
a.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 结果大吃一惊
开始2020-04-09 18:16:10
A0取出来了===1
A0更新完了===2
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B0取出来了===2
B0更新完了===3
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A1取出来了===3
A1更新完了===4
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B1取出来了===4
B1更新完了===5
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A2取出来了===5
A2更新完了===6
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B2取出来了===6
B2更新完了===7
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A3取出来了===6
A3更新完了===7
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B3取出来了===7
B3更新完了===8
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A4取出来了===8
A4更新完了===9
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B4取出来了===9
B4更新完了===10
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A5取出来了===10
A5更新完了===11
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
B5取出来了===11
B5更新完了===12
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A6取出来了===12
A6更新完了===13
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
B6取出来了===13
B6更新完了===14
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
A7取出来了===14
A7更新完了===15
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
B7取出来了===15
B7更新完了===16
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
A8取出来了===16
A8更新完了===17
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
B8取出来了===17
B8更新完了===18
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
A9取出来了===18
A9更新完了===19
结束2020-04-09 18:16:12
开始2020-04-09 18:16:12
B9取出来了===19
B9更新完了===20
结束2020-04-09 18:16:12
上面是打印出来的日志记录,可以发现:
开始2020-04-09 18:16:11
B2取出来了===6
B2更新完了===7
结束2020-04-09 18:16:11
开始2020-04-09 18:16:11
A3取出来了===6
A3更新完了===7
结束2020-04-09 18:16:11
也就是说B2和A3似乎同时获得了synchronize同步锁,这........
二.问题分析
- 其实我们可以肯定的是synchronize同步锁肯定是正确的,这里是因为,在spring中我们知道事务管理是通过aop实现的,所以我们加入了事务支持,那么这个controller方法就已经设计了aop了,所以事实上在项目启动中,会动态生成一个代理类,所以我们得到持久化操作可能还没有结束,但是同步方法已经结束了,锁已经释放了,所以就会造成这样的问题;
三.问题解决
- 我们可以在service中加入事务支持,这样在controller中调用持久化操作,这样代理类就是以service为真实类了,我们的controller就不会被代理了,所以就不会出现上述问题了
@Service
public class CommonService {
@Autowired
UserMapper userMapper;
@Transactional
public User getUserByUid(String upid)
{
return userMapper.getUserByUid(upid);
}
@Transactional
public void updateUser(User user)
{
userMapper.updateUser(user);
}
}
@ResponseBody
@GetMapping("/up")
public String up(String upid,HttpServletRequest request) {
synchronized (this)
{
System.out.println("开始"+new SimpleDateFormat("yyyy-MM-dd HH🇲🇲ss").format(new Date()));
User loginUser = (User)request.getSession().getAttribute("loginUser");
User userTemp = commonService.getUserByUid(upid);
System.out.println(request.getHeader("User-Agent")+"取出来了==="+userTemp.getUpcount());
userTemp.setUpcount(userTemp.getUpcount()+1);
commonService.updateUser(userTemp);
System.out.println(request.getHeader("User-Agent")+"更新完了==="+userTemp.getUpcount());
System.out.println("结束"+new SimpleDateFormat("yyyy-MM-dd HH🇲🇲ss").format(new Date()));
return String.valueOf(userTemp.getUpcount());
}
}
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名,转载请标明出处
最后编辑时间为:
2020/04/09 18:29