个人博客--小小笔记3(ThreadLocal内存泄漏、使用线程池异步更新阅读次数和评论次数、int和interger类型比较、Long精度损失自定义AOP日志、获取本地时间、数据库时间类型转换)

发布于:2023-01-12 ⋅ 阅读:(383) ⋅ 点赞:(0)

目录

一、ThreadLocal内存泄漏(05.md)

二、使用线程池更新阅读和增加评论次数

1.线程配置类,开启多线程

2.编写逻辑、引用线程

三、int基本类型和interger、精度损失

1、interger代替int

2、防止前端 精度损失 把id转为string

四、发布文章(07.md)

五、AOP日志(自定义注解)

1、先自定义基本信息

2、利用aop切面实现日志

3、导入工具类,可网上搜索

4、使用自定义的工具类

 六、获取时间的方法、数据库时间类型转换

1、获取时间的方法

 2、数据库时间类型转换

七、文章图片上传(上传至云服务器七牛云而不是本地)08.md

 上传至本地服务器,可见Java项目实战——瑞吉外卖day04


一、ThreadLocal内存泄漏(05.md)

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

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

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

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

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

二、使用线程池更新阅读和增加评论次数

线程池保证两操作不干扰、写线程方法执行逻辑,写线程配置类开启多线程

1.线程配置类,开启多线程

@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {
    @Bean("taskExecutor") //task任务、Executor执行器
    public Executor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        //配置队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("liu-2000博客");
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //执行初始化
        executor.initialize();
        return executor;
    }
}

2.编写逻辑、引用线程

package com.study.blog.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.study.blog.dao.mapper.ArticleMapper;
import com.study.blog.dao.pojo.Article;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class ThreadService{

    @Async("taskExecutor")
    public void updateViewCount(ArticleMapper articleMapper, Article article) {
        //只更改阅读数,新创建一个对象专用于更改此属性,减少sql操作
        Article articleUpdate = new Article();
        articleUpdate.setViewCounts(article.getViewCounts()+1);
        //增加方法执行条件,类似于乐观锁
        LambdaQueryWrapper<Article> qw = new LambdaQueryWrapper<>();
        //确保执行前id和阅读数一致,避免其他线程抢占,类似于乐观锁
        qw.eq(Article::getViewCounts,article.getViewCounts());
        qw.eq(Article::getId,article.getId());
        articleMapper.update(articleUpdate,qw);
        try {
            Thread.sleep(5000);
            System.out.println("增加阅读数++++异步线程更新结束.......@Async引入多线程........@EnableAsync开启对应线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Async("taskExecutor")
    public void updateCommentCount(ArticleMapper articleMapper, Article article) {
        //只更改阅读数,新创建一个对象专用于更改此属性,减少sql操作
        Article articleUpdate = new Article();
        articleUpdate.setCommentCounts(article.getCommentCounts()+1);
        //增加方法执行条件,类似于乐观锁
        LambdaQueryWrapper<Article> qw = new LambdaQueryWrapper<>();
        //确保执行前id和阅读数一致,避免其他线程抢占,类似于乐观锁
        qw.eq(Article::getId,article.getId());
        qw.eq(Article::getCommentCounts,article.getCommentCounts());
        articleMapper.update(articleUpdate,qw);
        try {
            Thread.sleep(5000);
            System.out.println("增加评论数++++异步线程更新结束.......@Async引入多线程........@EnableAsync开启对应线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

三、int基本类型和interger、精度损失

1、interger代替int

int基本类型在执行操作时会默认赋值0,所以在某些操作时若关联到int类型的属性,
但我们又不对其进行操作,最终结果mybatis还是会将int属性赋值0然后返回,需用interger代替int

之前Article中的commentCounts,viewCounts,weight 字段为int,会造成更新阅读次数的时候,将其余两个字段设为初始值0

2、防止前端 精度损失 把id转为string

// 分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

四、发布文章(07.md)

请求参数过多且相互关联,插入到数据库时需注意先后顺序

 此处涉及到利用本地线程存储的sysUser信息给数据库赋值用户id,所以需要将 为线程 set用户信息的登录拦截器设置此方法路径(/articles/publish) 为拦截路径

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/test")
                .addPathPatterns("/comments/create/change")
                .addPathPatterns("/articles/publish");
    }
//利用之前写的工具类来获取用户id----->需要将此功能设置拦截,不然sysUser为null
        SysUser sysUser = UserThreadLocal.get();
        article.setAuthorId(sysUser.getId());

MP提供的BaseMapper在进行insert时会自动生产id

前端传来的参数,后端若使用自定义实体类接收,应将参数名称保持一致,否则接收null

五、AOP日志(自定义注解)

1、先自定义基本信息

//Type 代表可以放在类上面 Method 代表可以放在方法上
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {

    String module() default "";

    String operator() default "";
}

2、利用aop切面实现日志


@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {

    @Pointcut("@annotation(com.study.blog.common.aop.LogAnnotation)")
    public void pt(){}

    //环绕通知
    @Around("pt()")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = joinPoint.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        recordLog(joinPoint, time);
        return result;
    }

    private void recordLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        log.info("=====================log start================================");
        log.info("module:{}",logAnnotation.module());
        log.info("operation:{}",logAnnotation.operator());

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("request method:{}",className + "." + methodName + "()");

//        //请求的参数
        Object[] args = joinPoint.getArgs();
        String params = JSON.toJSONString(args[0]);
        log.info("params:{}",params);

        //获取request 设置IP地址
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        log.info("ip:{}", IpUtils.getIpAddr(request));


        log.info("excute time : {} ms",time);
        log.info("=====================log end================================");
    }
}

3、导入工具类,可网上搜索

4、使用自定义的工具类

   @PostMapping
    @LogAnnotation(module="文章",operator="获取文章列表")
    public R listArticle(@RequestBody PageParams pageParams){
        return articleService.listArticle(pageParams);
    }

 效果

 六、获取时间的方法、数据库时间类型转换

1、获取时间的方法

获取当前时间毫秒数----》System.currentTimeMillis()

获取标准时间----》LocalDateTime.now()

 2、数据库时间类型转换

正常标准时间的话直接用year、month等函数就可

毫秒值的话用 FROM_UNIXTIME(时间戳)

select FROM_UNIXTIME(create_date/1000,'%Y') as year,
       FROM_UNIXTIME(create_date/1000,'%m') as month,
count(*) as count 
from ms_article 
group by year,month

七、文章图片上传(上传至云服务器七牛云而不是本地)08.md

为避免主服务器卡断(因带宽大小限制),使用云服务器实现上传功能
七牛云--对象存储--空间管理--注意:返回给前端的url应与存放到服务器中的url一致,一定要拼接正确

 具体使用方法可见08.md或者https://developer.qiniu.com/kodo/1239/java

 上传至本地服务器,可见Java项目实战——瑞吉外卖day04

本文含有隐藏内容,请 开通VIP 后查看