什么是线程安全? 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
在个人博客系统中,我们有一个主要的业务是去查看文章详情。(如下)
简单分析一下业务逻辑,用户点击文章链接,前台把文章id传到后台,后台根据文章id去查询作者以及文章详情还要文章发布时间,还要阅读人数。其实后面还要文章的评论,不过这个是另外的接口,暂时不讨论。
控制层
/**
* 文章详情
* @param articleId
* @return
*/
@PostMapping("view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId){
return articleService.findArticleById(articleId);
}
service层(接口分离)
/**
* 文章详情
* @param articleId
* @return
*/
Result findArticleById(Long articleId);
实现类
public Result findArticleById(Long articleId) {
/**
* 1. 根据id查询 文章信息
* 2. 根据bodyId和categoryid 去做关联查询
*/
Article article=this.articleMapper.selectById(articleId);
ArticleVo articleVo=copy(article,true,true,true,true);
threadService.updateArticleViewCount(articleMapper,article);
return Result.success(articleVo);
}
这里我们使用ThreadService线程池,是我们不想在主线程findArticleById中去更新操作,因为这样
- 更新操作与主线程无关
- 更新操作需要加锁,会阻塞其他读操作,性能会降低
因此我们通过线程池,把更新操作扔到线程池中执行,与主线程无关
@Component
public class ThreadService {
//期望从操作在线程池中进行 不会影响原主进程
@Async("taskExecutor")
public void updateArticleViewCount(ArticleMapper articleMapper, Article article) {
int viewCounts = article.getViewCounts();
Article articleUpdate= new Article();
articleUpdate.setViewCounts(viewCounts+1);
LambdaUpdateWrapper<Article> updateWrapper=new LambdaUpdateWrapper<>();
updateWrapper.eq(Article::getId,article.getId());
//设置一个 为了在多线程的环境下 线程安全
updateWrapper.eq(Article::getViewCounts,viewCounts);
// update article set view_count=100 where view_count=99 and id=11
articleMapper.update(articleUpdate,updateWrapper);
}
}
特别需要注意,我们的sql语句其实是下面这样的形式。多出来view_count=old_view是基于线程安全的考虑,实际上是达到了加锁目的
update article set view_count=100 where view_count=99 and id=11