个人博客项目笔记_05

1. 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. 文章详情

2.1 接口说明

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

请求方式:POST

请求参数:

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

返回数据:

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

2.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.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.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.5 测试

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

3.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.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.3 测试

  • 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.3 测试

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/538147.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【力扣题】关于单链表和数组习题

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

蓝桥杯-单片机基础20——第14届省赛真题代码详解

网上传言说14届最难&#xff0c;实际做下来感觉确实是这样的 本试题&#xff0c;需要将矩阵键盘短接&#xff0c;J3的555定时器输出口与P34短接 1.比赛题目 2.编程的大致思路 首先&#xff0c;完成基础代码与数码管的不同窗口&#xff0c;定义大部分要用到的变量&#xff0c…

Qt/QML编程之路:HVAC control(51)

空调控制是智能座舱的重要组成部分。传统汽车里,空调控制一般通过机械按钮实现,比如内循环、空调on/off开关、空调风量调节、专门的前挡风玻璃吹风加热等,出风口可以手动调节。 过去很多年一直是机械按键,随着新能源汽车的大势所趋,空调控制方式也逐渐被颠覆。 现在这些控…

【自然语言】使用词袋模型,TF-IDF模型和Word2Vec模型进行文本向量化

一、任务目标 python代码写将 HarryPorter 电子书作为语料库&#xff0c;分别使用词袋模型&#xff0c;TF-IDF模型和Word2Vec模型进行文本向量化。 1. 首先将数据预处理&#xff0c;Word2Vec 训练时要求考虑每个单词前后的五个词汇&#xff0c;地址为 作为其上下文 &#xf…

如何开辟动态二维数组(C语言)

1. 开辟动态二维数组 C语言标准库中并没有可以直接开辟动态二维数组的函数&#xff0c;但我们可以通过动态一维数组来模拟动态二维数组。 二维数组其实可以看作是一个存着"DataType []"类型数据的一维数组&#xff0c;也就是存放着一维数组地址的一维数组。 所以&…

CRMEB 多商户和多店版的区别

&#xff08;1&#xff09;两个系统根本属性不同 CRMEB多商户是一款B2B2C多业态商家入驻平台系统&#xff0c;通俗点说&#xff0c;就是一个商城系统有多个商家运营各自的店铺。 多商户系统具有联营、自营、招商、混合等多种运营模式&#xff0c;主要用来构建类似京东、淘宝的…

【二分算法】

17. 二分查找&#xff08;easy&#xff09; 算法流程&#xff1a; 算法代码&#xff1a; int search(int* nums, int numsSize, int target) {// 初始化 left 与 right 指针int left 0, right numsSize - 1;// 由于两个指针相交时&#xff0c;当前元素还未判断&#xff0c;因…

2024最新仿默往IM即时通讯系统源码(PC+WEB+IOS+Android)客户端

简介: 2024最新仿默往IM即时通讯系统源码(PC+WEB+IOS+Android)客户端 系统功能配置灵活、海量并发、稳定可靠、数据安全,2小时快速部署、数据安全、单聊群聊、系统通知等通信功能,支持App、PC、Web等多端快速接入。 群功能:设置群二维码,群公告,昵称,头像,群共享文件…

零基础教程|四步学会自制宣传手册

在当今竞争激烈的市场中&#xff0c;一本精美而引人注目的宣传手册是吸引客户和推广产品的重要工具。但对于许多人来说&#xff0c;制作宣传手册似乎是一项艰巨的任务&#xff0c;特别是对于零基础的人来说。然而&#xff0c;通过以下四个简单的步骤&#xff0c;您也可以轻松学…

解决redis乱码问题

目录 1.问题 2.查看redis序列化机制 3.设置redis的序列化器 1.问题 在使用redis最为缓存时&#xff0c;发现key乱码问题 这是由于redis的序列化机制导致的 2.查看redis序列化机制 3.设置redis的序列化器 Configuration Data public class RedisConfig {/*** redis序列化*…

应急响应-战前反制主机HIDSElkeid蜜罐系统HFish

知识点 战前-反制-平台部署其他更多项目&#xff1a; https://github.com/birdhan/SecurityProduct HIDS&#xff1a;主机入侵检测系统&#xff0c;通常会有一个服务器承担服务端角色&#xff0c;其他主机就是客户端角色&#xff0c;客户端加入到服务端的检测范围里&#xff…

【漏洞复现】通天星CMSV6车载视频监控平台MobileAction文件读取漏洞

Nx01 产品简介 通天星车载视频监控平台软件拥有多种语言版本&#xff0c;应用于公交车车载视频监控、校车车载视频监控、大巴车车载视频监控、物流车载监控、油品运输车载监控等公共交通上。 Nx02 漏洞描述 通天星CMSV6车载视频监控平台MobileAction文件读取漏洞&#xff0c;攻…

C语言---顺序表(二)

文章目录 前言1.准备工作2.代码的实现2.1.顺序表的创建、销毁和打印2.2.顺序表的扩容、头插\删、尾插\删2.2.1.扩容2.2.2.尾插2.2.3.头插2.2.3.尾删2.2.4.头删 2.3.指定位置之前插入/删除数据/查找数据2.3.1.指定位置之前插入数据2.3.2.指定位置之前删除数据2.3.3.查找特定数据…

ChatGPT基础(二) ChatGPT的使用和调优

文章目录 ChatGPT的特性采用关键词进行提问给ChatGPT指定身份提升问答质量的策略1.表述方式上的优化2.用"继续"输出长内容3.营造场景4.由浅入深&#xff0c;提升问题质量5.预设回答框架和风格 ChatGPT的特性 1.能够联系上下文进行回答 ChatGPT回答问题是有上下文的&…

Samba实现windows和Linux共享文件,环境搭建

​ 搭建步骤 安装sambad sudo apt-get install samba samba-common 创建samba用户和密码 此处使用 Linux 账号和密码作为 samba 的账号和密码。Linux 账号为 shelmean shelmeanmachine:[~] $ sudo smbpasswd -a shelmean New SMB password: Retype new SMB password: Add…

MongoDB副本集部署(windows)

环境准备 本教程演示mongodb4.4 副本集部署&#xff08;一主两从&#xff0c;伪分布式&#xff09; 节点配置主节点localhost:27017从节点1localhost:27018从节点2localhost:27019 每一个节点&#xff08;实例&#xff09;都创建对应的数据文件&#xff08;data&#xff09;…

记一次空迭代器导致的崩溃分析

一. 崩溃代码&#xff1a; class EasySelect::Impl { public:Impl() default;std::vector<int> waitForReadable ();void addFd (int fd);void removeFd (int fd);void stopWait ();private:std::vector<int> m_fds;std::mutex m_fdsMutex;std::mutex m_pipeMute…

MySQL数据导出导出的三种办法(13/16)

数据导入导出 基本概述 目前常用的有3中数据导入与导出方法&#xff1a; 使用mysqldump工具&#xff1a; 优点&#xff1a; 简单易用&#xff0c;只需一条命令即可完成数据导出。可以导出表结构和数据&#xff0c;方便完整备份。支持过滤条件&#xff0c;可以选择导出部分数据…

python知识点汇总(十一)

python知识点总结 1、当Python退出时&#xff0c;是否会清除所有分配的内存&#xff1f;2、Python的优势有哪些&#xff1f;3、什么是元组的解封装4、Python中如何动态获取和设置对象的属性&#xff1f;5、创建删除操作系统上的文件6、主动抛出异常7、help() 函数和 dir() 函数…