隐藏页面特效

个人博客项目笔记_05

1|01. ThreadLocal内存泄漏


ThreadLocal 内存泄漏是指由于没有及时清理 ThreadLocal 实例所存储的数据,导致这些数据在线程池或长时间运行的应用中累积过多,最终导致内存占用过高的情况。

内存泄漏通常发生在以下情况下:

  1. 线程池场景下的 ThreadLocal 使用不当: 在使用线程池时,如果线程被重用而没有正确清理 ThreadLocal 中的数据,那么下次使用这个线程时,它可能会携带上一次执行任务所遗留的数据,从而导致数据累积并消耗内存。
  2. 长时间运行的应用中未清理 ThreadLocal 数据: 在一些长时间运行的应用中,比如 Web 应用,可能会创建很多 ThreadLocal 实例并存储大量数据。如果这些数据在使用完后没有及时清理,就会导致内存泄漏问题。
  3. 没有使用 remove() 方法清理 ThreadLocal 数据: 在使用完 ThreadLocal 存储的数据后,如果没有调用 remove() 方法清理数据,就会导致数据长时间存在于 ThreadLocal 中,从而可能引发内存泄漏。

实线代表强引用,虚线代表弱引用

每一个Thread维护一个ThreadLocalMap, key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

强引用,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

弱引用,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

2|02. 文章详情


2|12.1 接口说明


接口url:/articles/view/{id}

请求方式:POST

请求参数:

参数名称 参数类型 说明
id long 文章id(路径参数)

返回数据:

{ "success": true, "code": 200, "msg": "success", "data": "token" }

2|22.2 涉及到的表


CREATE TABLE `blog`.`ms_article_body` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `article_id` bigint(0) NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `article_id`(`article_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.cherriesovo.blog.dao.pojo; import lombok.Data; @Data public class ArticleBody { //文章详情表 private Long id; private String content; private String contentHtml; private Long articleId; }
#文章分类 CREATE TABLE `blog`.`ms_category` ( `id` bigint(0) NOT NULL AUTO_INCREMENT, `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.cherriesovo.blog.dao.pojo; import lombok.Data; @Data public class Category { private Long id; private String avatar; private String categoryName; private String description; }

2|32.3 Controller


//json数据进行交互 @RestController @RequestMapping("articles") public class ArticleController { /* * 通过id获取文章 * */ @PostMapping("view/{id}") //@PathVariable("id") 注解用于将 URL 中的 {id} 赋值给 articleId 参数。 public Result findArticleById(@PathVariable("id") Long articleId) { return articleService.findArticleById(articleId); } }

2|42.4 Service


ArticleVo findArticleById(Long id);
public interface ArticleService { //查看文章详情 Result findArticleById(Long articleId); }
package com.cherriesovo.blog.vo; import lombok.Data; import java.util.List; @Data public class ArticleVo { private Long id; private String title; private String summary; private int commentCounts; private int viewCounts; private int weight; /** * 创建时间 */ private String createDate; private String author; private ArticleBodyVo body; private List<TagVo> tags; private CategoryVo category; }
@Service public class ArticleServiceImpl implements ArticleService { @Autowired private ArticleMapper articleMapper; @Autowired private TagService tagService; @Autowired private SysUserService sysUserService; @Autowired private CategoryService categoryService; public ArticleVo copy(Article article,boolean isAuthor,boolean isBody,boolean isTags,boolean isCategory){ ArticleVo articleVo = new ArticleVo(); BeanUtils.copyProperties(article, articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); //并不是所有的接口都需要标签,作者信息 if(isTags){ Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); } if(isAuthor){ Long authorId = article.getAuthorId(); //getNickname()用于获取某个对象或实体的昵称或别名 articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } if (isBody){ Long bodyId = article.getBodyId(); articleVo.setBody(findArticleBodyById(bodyId)); } if (isCategory){ Long categoryId = article.getCategoryId(); articleVo.setCategory(categoryService.findCategoryById(categoryId)); } return articleVo; } private List<ArticleVo> copyList(List<Article> records,boolean isAuthor,boolean isBody,boolean isTags) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article article : records) { ArticleVo articleVo = copy(article,isAuthor,false,isTags,false); articleVoList.add(articleVo); } return articleVoList; } private List<ArticleVo> copyList(List<Article> records,boolean isAuthor,boolean isBody,boolean isTags,boolean isCategory) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article article : records) { ArticleVo articleVo = copy(article,isAuthor,isBody,isTags,isCategory); articleVoList.add(articleVo); } return articleVoList; } @Autowired private ArticleBodyMapper articleBodyMapper; private ArticleBodyVo findArticleBodyById(Long bodyId) { ArticleBody articleBody = articleBodyMapper.selectById(bodyId); ArticleBodyVo articleBodyVo = new ArticleBodyVo(); articleBodyVo.setContent(articleBody.getContent());//setContent()是articleBodyVo的set方法 return articleBodyVo; } @Override public List<ArticleVo> listArticlesPage(PageParams pageParams) { // 分页查询article数据库表 QueryWrapper<Article> queryWrapper = new QueryWrapper<>(); Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize()); Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); List<ArticleVo> articleVoList = copyList(articlePage.getRecords(),true,false,true); return articleVoList; } @Override public Result hotArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getViewCounts); //根据浏览量倒序 queryWrapper.select(Article::getId,Article::getTitle); queryWrapper.last("limit " + limit); //select id,title from article order by view_counts desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles,false,false,false)); } @Override public Result newArticles(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getCreateDate); queryWrapper.select(Article::getId,Article::getTitle); queryWrapper.last("limit "+limit); //select id,title from article order by create_date desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles,false,false,false)); } @Override public Result listArchives() { List<Archives> archivesList = articleMapper.listArchives(); return Result.success(archivesList); } @Override public Result findArticleById(Long articleId) { /* * 1、根据id查询文章信息 * 2、根据bodyId和categoryId去做关联查询 * */ Article article = this.articleMapper.selectById(articleId); ArticleVo articleVo = copy(article, true, true, true,true); return Result.success(articleVo); } }
package com.cherriesovo.blog.vo; import lombok.Data; @Data public class CategoryVo { private Long id; private String avatar; private String categoryName; }
package com.cherriesovo.blog.vo; import lombok.Data; @Data public class ArticleBodyVo { private String content; }
package com.cherriesovo.blog.service; import com.cherriesovo.blog.vo.CategoryVo; import java.util.List; public interface CategoryService { CategoryVo findCategoryById(Long categoryId); }
@Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Override public CategoryVo findCategoryById(Long categoryId) { Category category = categoryMapper.selectById(categoryId); CategoryVo categoryVo = new CategoryVo(); BeanUtils.copyProperties(category,categoryVo); return categoryVo; } }
package com.cherriesovo.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.cherriesovo.blog.dao.pojo.ArticleBody; public interface ArticleBodyMapper extends BaseMapper<ArticleBody> { }
package com.cherriesovo.blog.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.cherriesovo.blog.dao.pojo.Category; public interface CategoryMapper extends BaseMapper<Category> { }

2|52.5 测试


3|03. 使用线程池 更新阅读次数


3|13.1 线程池配置


  1. taskExecutor 是一个线程池对象,在这段代码中通过 @Bean("taskExecutor") 注解定义并配置了一个线程池,并将其命名为 "taskExecutor"。
  2. asyncServiceExecutor() 方法是一个 Bean 方法,用于创建并配置一个线程池,并以 taskExecutor 作为 Bean 的名称。
  3. ThreadPoolTaskExecutor 是 Spring 框架提供的一个实现了 Executor 接口的线程池
  4. 在方法中创建了一个 ThreadPoolTaskExecutor 实例 executor,并对其进行了一系列配置:
    • setCorePoolSize(5): 设置核心线程数为 5,即线程池在空闲时会保持 5 个核心线程。
    • setMaxPoolSize(20): 设置最大线程数为 20,即线程池中允许的最大线程数量。
    • setQueueCapacity(Integer.MAX_VALUE): 配置队列大小为整数的最大值,即任务队列的最大容量。
    • setKeepAliveSeconds(60): 设置线程活跃时间为 60 秒,即线程在空闲超过该时间后会被销毁。
    • setThreadNamePrefix("CherriesOvO博客项目"): 设置线程名称的前缀为 "CherriesOvO博客项目"。
    • setWaitForTasksToCompleteOnShutdown(true): 设置在关闭线程池时等待所有任务结束。
    • initialize(): 执行线程池的初始化。
  5. 最后,将配置好的线程池返回为一个 Executor Bean,供其他组件使用。
package com.cherriesovo.blog.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; @Configuration @EnableAsync //开启多线程 public class ThreadPoolConfig { @Bean("taskExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置核心线程数 executor.setCorePoolSize(5); // 设置最大线程数 executor.setMaxPoolSize(20); //配置队列大小 executor.setQueueCapacity(Integer.MAX_VALUE); // 设置线程活跃时间(秒) executor.setKeepAliveSeconds(60); // 设置默认线程名称 executor.setThreadNamePrefix("CherriesOvO博客项目"); // 设置等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); //执行初始化 executor.initialize(); return executor; } }

3|23.1 使用


  1. 通过 @Async("taskExecutor") 注解,该方法标记为异步执行,并指定了使用名为 "taskExecutor" 的线程池。

  2. articleMapper.update(articleUpdate, updateWrapper) 是一个 MyBatis-Plus 中的更新操作,用于更新数据库中的文章记录。

    update 方法接受两个参数:

    1. articleUpdate:表示需要更新的文章对象,其中包含了新的阅读量。
    2. updateWrapper:表示更新条件,即确定哪些文章需要被更新的条件。
  3. 这段代码通过 Thread.sleep(5000) 方法在当前线程中休眠了5秒钟。这样做的目的是为了模拟一个耗时操作,以展示在异步线程中执行的任务不会影响到主线程的执行。

package com.cherriesovo.blog.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.cherriesovo.blog.dao.mapper.ArticleMapper; import com.cherriesovo.blog.dao.pojo.Article; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @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); LambdaQueryWrapper<Article> updateWrapper = new LambdaQueryWrapper<>(); updateWrapper.eq(Article::getId,article.getId()); //设置一个 为了在多线程环境下 线程安全 updateWrapper.eq(Article::getViewCounts,article.getViewCounts()); //update article set view_count=? where view_count=? and id=? articleMapper.update(articleUpdate,updateWrapper); try { //睡眠5秒 证明不会影响主线程的使用,5秒后数据才会出现 Thread.sleep(5000); // System.out.println("更新完成了"); } catch (InterruptedException e) { e.printStackTrace(); } } }
@Service public class ArticleServiceImpl implements ArticleService { @Autowired private ThreadService threadService; @Override 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); } }

3|33.3 测试


睡眠 ThredService中的方法 5秒,不会影响主线程的使用,即文章详情会很快的显示出来,不受影响


__EOF__

本文作者CherriesOvO
本文链接https://www.cnblogs.com/zyj3955/p/18127298.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   CherriesOvO  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2021-04-10 关于ECharts图表反复修改都无法显示的解决方案
点击右上角即可分享
微信分享提示