不懂咱就学,记不住多看几遍(二)

2024-02-19 19:36
文章标签 记不住 就学 几遍

本文主要是介绍不懂咱就学,记不住多看几遍(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Redis分布式锁中加锁与解锁、过期如何续命

实现要点:

  1. 互斥性,同一时刻,只能有一个客户端持有锁。
  2. 防止死锁发生,如果持有锁的客户端因崩溃而没有主动释放锁,也要保证锁可以释放并且其他客户端可以正常加锁。
  3. 加锁和释放锁必须是同一个客户端。
  4. 容错性,只要redis还有节点存活,就可以进行正常的加锁解锁操作。

加锁:直接使用set命令同时设置唯一id和过期时间;

解锁:加锁之后可以返回唯一id,标志此锁是该客户端锁拥有;释放锁时要先判断拥有者是否是自己,然后删除,这个需要redis的lua脚本保证两个命令的原子性执行。

@Slf4j
public class RedisDistributedLock {private static final String LOCK_SUCCESS = "OK";private static final Long RELEASE_SUCCESS = 1L;private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";// 锁的超时时间private static int EXPIRE_TIME = 5 * 1000;// 锁等待时间private static int WAIT_TIME = 1 * 1000;private Jedis jedis;private String key;public RedisDistributedLock(Jedis jedis, String key) {this.jedis = jedis;this.key = key;}// 不断尝试加锁public String lock() {try {// 超过等待时间,加锁失败long waitEnd = System.currentTimeMillis() + WAIT_TIME;String value = UUID.randomUUID().toString();while (System.currentTimeMillis() < waitEnd) {String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME);if (LOCK_SUCCESS.equals(result)) {return value;}try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}} catch (Exception ex) {log.error("lock error", ex);}return null;}public boolean release(String value) {if (value == null) {return false;}// 判断key存在并且删除key必须是一个原子操作// 且谁拥有锁,谁释放String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = new Object();try {result = jedis.eval(script, Collections.singletonList(key),Collections.singletonList(value));if (RELEASE_SUCCESS.equals(result)) {log.info("release lock success, value:{}", value);return true;}} catch (Exception e) {log.error("release lock error", e);} finally {if (jedis != null) {jedis.close();}}log.info("release lock failed, value:{}, result:{}", value, result);return false;}
}

过期续命:守护线程续命,额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。Redisson 里面就实现了这个方案,使用 “看门狗” 定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间。

在获取锁成功后,给锁加一个 watchdog,watchdog 会起一个定时任务,在锁没有被释放且快要过期的时候会续期。

二、Spring的ApplicationEvent的使用场景

实现观察者模式的方法,ApplicationContextAware 我们可以把系统中所有ApplicationEvent传播给系统中所有的ApplicationListener;

三、SpringMVC拦截器和过滤器区别

1 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
2 过滤器是servlet规范规定的,只能用于web程序中,而拦截器是在spring容器中,它不依赖servlet容器。
3 过滤器可以拦截几乎所有的请求(包含对静态资源的请求),而拦截器只拦截action请求(不拦截静态资源请求)。
4 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
6 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
7 拦截器是被包裹在过滤器之中。
Filter pre -> doService -> dispatcher -> preHandle -> controller -> postHandle -> afterCompletion -> Filter after

四、一个项目中可以有多个dispatcherServelt吗?为什么?

可以配置,优先级不同;

<load-on-startup>1</load-on-startup>是启动顺序,让这个Servlet随Servletp容器一起启动。
<url-pattern>*.form</url-pattern>会拦截*.form结尾的请求。
<servlet-name>example</servlet-name>

五、SpringAOP涉及到什么设计模式?底层原理?

使用到的模式:适配器模式 单例模式 责任链模式 简单工厂 观察者 模版 代理 策略

【1】AOP的设计
    在Spring的底层,如果我们配置了代理模式,Spring会为每一个Bean创建一个对应的ProxyFactoryBean的FactoryBean来创建某个对象的代理对象。
    每个 Bean 都会被 JDK 或者 Cglib 代理。取决于是否有接口。
    每个 Bean 会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。
    当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是职责连的设计。

【2】代理的创建
    首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。
    创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。
    当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。
    注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。

【3】代理的调用
    当对代理对象进行调用时,就会触发外层拦截器。
    外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。
    当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。

五、SpringBoot配置文件加载优先级

1.命令行参数
2.jar包外部的application-{profile}.propertie或application.yml(带spring.profile)配置文件
3.jar包内部的application-{profile}.propertie或application.yml(带spring.profile)配置文件
4.jar包外部的application.propertie或application.yml(不带spring.profile)配置文件
5.jar包内部的application.propertie或application.yml(不带spring.profile)配置文件

六、SpringBoot启动原理

1.从MANIFEST.MF可以看到Main函数是JarLauncher
2.JarLauncher先找到自己所在的目标jar的路径,然后创建了一个Archive。
3.获取lib/下面的jar,并创建一个LaunchedURLClassLoader
4.再从MANIFEST.MF里读取到Start-Class,然后创建一个新的线程来启动应用的Main函数。

七、MySQL聚簇索引

数据库的索引从不同的角度可以划分成不同的类型,聚簇索引便是其中一种。

聚簇索引英文是 Clustered Index,有时候小伙伴们可能也会看到有人将之称为聚集索引等,与之相对的是非聚簇索引或者二级索引。

聚簇索引并不是一种单独的索引类型,而是一种数据的存储方式。在 MySQL 的 InnoDB 存储引擎中,所谓的聚簇索引实际上就是在同一个 B+Tree 中保存了索引和数据行:此时,数据放在叶子结点中,聚簇聚簇,意思就是说数据行和对应的键值紧凑的存在一起。

假设我有如下数据:

那么它的聚簇索引大概就是这个样子:

MySQL 表中的数据在磁盘中只可能保存一份,不可能保存两份,所以,在一个表中,聚簇索引只可能有一个,不可能有多个。

聚簇索引和主键

在 MySQL 中,如果表本身就有设置主键,那么主键就是聚簇索引;如果表本身没有设置主键,则会选择表中的一个唯一且非空的索引来作为聚簇索引;如果表中连唯一非空的索引都没有,那么就会自动选择表中的隐式主键来作为聚簇索引。

1.聚簇索引不一定是主键索引。

2.主键索引一定是聚簇索引。

最佳实践

在使用聚簇索引的时候,主键最好不要使用 UUID 这种随机字符串,使用 UUID 随机字符串至少存在两方面的问题:

    1.插入效率低,因为插入可能会导致页分裂,这个前面已经说过了。

    2.UUID 字符串所占用的存储空间远远大于一个 bigint,如果使用 UUID 来做主键,意味着在二级索引中,一个叶子结点能够存储的主键值就非常有限,进而可能会导致树增高,搜索时候 IO 次数增多,性能下降。

所以相对来说,主键自增会优于 UUID。

八、重写和重载,参数列表相同,只是泛型不同,会不会报错

重载: 发生在 同一个类中方法名相同而参数列表不同(类型,个数,顺序),返回值类型访问修饰符 可以相同也可以不同。

虽然在方法重载中可以使两个方法的返回值类型不同,但是只有返回值类型不同并不足以区分两个方法的重载,还需要通过参数列表来设置。

重写: 发生在 父子类中方法名和参数列表必须相同 ,返回值类型小于等于父类(即与被重写的方法的返回值类型相同或者是其子类),抛出的异常范围小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为 private ,则子类不能重写该方法。

九、写一个单例(如何防止反射反序列化破坏)

饿汉式:线程安全,调用效率高,不可延时加载

public class Singleton {//单例,构造方法不能给到外界调用private Singleton() {};//加载类时就直接创建对象private static Singleton instance = new Singleton();//将对象的调用方法暴露给外界public static Singleton getInstance(){return instance;}
}

饱汉式:线程安全,调用效率低,可延时加载

public static volatic Singleton  {//单例,构造方法不能给到外界调用private Singleton() {};//饱汉,调用时才实例化,多线程需要禁止指令重排序,且变量的改变需要对其它线程都可见,必须使用volatileprivate static volatile Singleton instance = null;//将对象的调用方法暴露给外界public static Singleton getInstance() {if (null == instance){//对象未创建时,进入实例化对象语句块synchronized (Singleton.class){//需要考虑并发,多个线程进入语句块,保证只有一个线程能实例化对象if( null == instance)	{instance = new Singleton();	}}	}return instance;}
}

内部静态类: 具备饿汉式单例模式优点的同时,又可延迟加载

public class Singleton {//单例,构造方法不能给到外界调用private Singleton() {};//静态内部类可以访问外部类的静态属性和静态方法private static class Inner {private static Singleton instance = new Singleton();	}//将对象的调用方法暴露给外界public static Singleton getInstance(){return Inner.instance;}
}

枚举类:枚举单例模式可以防止反射去创建实例

public enum  EnumSingleton {//创建一个枚举对象,该对象天生为单例INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}

另一种防止反射的方式(修改构造函数,支持饿汉式、饱汉式、内部静态类):

/*** 单例防反射测试  饿汉式试验* @author Administrator*/
public class SingletonTest {private static boolean flag = false;//单例,构造方法不能给到外界调用private SingletonTest() {synchronized(SingletonTest.class) {if (false == flag) {flag = !flag;} else {throw new RuntimeException("单例模式正在被攻击");}}};//加载类时就直接创建对象private static SingletonTest instance = new SingletonTest();//将对象的调用方法暴露给外界public static SingletonTest getInstance(){return instance;}public static void main(String[] args) {try {Class<SingletonTest> classType = SingletonTest.class;Constructor<SingletonTest> constructor = classType.getDeclaredConstructor(null);constructor.setAccessible(true);SingletonTest singleton = (SingletonTest) constructor.newInstance();SingletonTest singleton2 = SingletonTest.getInstance();System.out.println(singleton == singleton2);} catch (Exception e) {e.printStackTrace();}}
}

可以参考:单例线程池小工具,轻量级使用,可以在小项目中通过一两句话使用线程池。_getsinglepool()-CSDN博客

内存级缓存ConcurrentSkipListMap实现方式_folly concurrentskiplistmap-CSDN博客 中的MemoryCache类

十、ArrayList底层原理

1.使用数组存储元素

2.动态扩容 初始化10,自增1.5倍

3.线程不安全

4.插入和删除元素需要移动数组,导致效率下降

5.随机访问:读取元素时效率较高

十一、手写一个定时的线程池

一篇文章让你彻底搞懂定时线程池ScheduledThreadPoolExecutor(深度剖析)-CSDN博客

十二、Java自带的序列化方式

JDK 自带的序列化,只需实现 java.io.Serializable接口即可

JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。

像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。

十三、项目中如何使用策略模式

if判断非常多的时候

public interface Strategy {public int doOperation(int num1, int num2);
}public class OperationAdd implements Strategy{@Overridepublic int doOperation(int num1, int num2) {return num1 + num2;}
}public class OperationSubtract implements Strategy{@Overridepublic int doOperation(int num1, int num2) {return num1 - num2;}
}public class Context {private Strategy strategy;public Context(Strategy strategy){this.strategy = strategy;}public int executeStrategy(int num1, int num2){return strategy.doOperation(num1, num2);}
}public class StrategyPatternDemo {public static void main(String[] args) {Context context = new Context(new OperationAdd());    System.out.println("10 + 5 = " + context.executeStrategy(10, 5));context = new Context(new OperationSubtract());      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));}
}

 参考: 五:策略模式 + 工厂方法

十四、Spring注解demo

注解类:

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*** 模块*/String title() default "";/*** 功能*/BusinessType businessType() default BusinessType.OTHER;/*** 操作人类别*/OperatorType operatorType() default OperatorType.MANAGE;/*** 是否保存请求的参数*/boolean isSaveRequestData() default true;/*** 是否保存响应的参数*/boolean isSaveResponseData() default true;/*** 排除指定的请求参数*/String[] excludeParamNames() default {};}

业务执行类:

/*** 操作日志记录处理** @author douzi*/
@Slf4j
@Aspect
@Component
public class LogAspect {/*** 排除敏感属性字段*/public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };/*** 处理完请求后执行** @param joinPoint 切点*/@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {handleLog(joinPoint, controllerLog, null, jsonResult);}/*** 拦截异常操作** @param joinPoint 切点* @param e         异常*/@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {handleLog(joinPoint, controllerLog, e, null);}protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {try {// *========数据库日志=========*//OperLogEvent operLog = new OperLogEvent();operLog.setStatus(BusinessStatus.SUCCESS.ordinal());// 请求的地址String ip = ServletUtils.getClientIP();operLog.setOperIp(ip);operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));operLog.setOperName(LoginHelper.getUsername());if (e != null) {operLog.setStatus(BusinessStatus.FAIL.ordinal());operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));}// 设置方法名称String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operLog.setMethod(className + "." + methodName + "()");// 设置请求方式operLog.setRequestMethod(ServletUtils.getRequest().getMethod());// 处理设置注解上的参数getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);// 发布事件保存数据库SpringUtils.context().publishEvent(operLog);} catch (Exception exp) {// 记录本地异常日志log.error("异常信息:{}", exp.getMessage());exp.printStackTrace();}}/*** 获取注解中对方法的描述信息 用于Controller层注解** @param log     日志* @param operLog 操作日志* @throws Exception*/public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {// 设置action动作operLog.setBusinessType(log.businessType().ordinal());// 设置标题operLog.setTitle(log.title());// 设置操作人类别operLog.setOperatorType(log.operatorType().ordinal());// 是否需要保存request,参数和值if (log.isSaveRequestData()) {// 获取参数的信息,传入到数据库中。setRequestValue(joinPoint, operLog, log.excludeParamNames());}// 是否需要保存response,参数和值if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));}}/*** 获取请求的参数,放到log中** @param operLog 操作日志* @throws Exception 异常*/private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());String requestMethod = operLog.getRequestMethod();if (MapUtil.isEmpty(paramsMap)&& HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);operLog.setOperParam(StringUtils.substring(params, 0, 2000));} else {MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);MapUtil.removeAny(paramsMap, excludeParamNames);operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));}}/*** 参数拼装*/private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {StringJoiner params = new StringJoiner(" ");if (ArrayUtil.isEmpty(paramsArray)) {return params.toString();}for (Object o : paramsArray) {if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {String str = JsonUtils.toJsonString(o);Dict dict = JsonUtils.parseMap(str);if (MapUtil.isNotEmpty(dict)) {MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);MapUtil.removeAny(dict, excludeParamNames);str = JsonUtils.toJsonString(dict);}params.add(str);}}return params.toString();}/*** 判断是否需要过滤的对象。** @param o 对象信息。* @return 如果是需要过滤的对象,则返回true;否则返回false。*/@SuppressWarnings("rawtypes")public boolean isFilterObject(final Object o) {Class<?> clazz = o.getClass();if (clazz.isArray()) {return clazz.getComponentType().isAssignableFrom(MultipartFile.class);} else if (Collection.class.isAssignableFrom(clazz)) {Collection collection = (Collection) o;for (Object value : collection) {return value instanceof MultipartFile;}} else if (Map.class.isAssignableFrom(clazz)) {Map map = (Map) o;for (Object value : map.values()) {return value instanceof MultipartFile;}}return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse|| o instanceof BindingResult;}
}

这篇关于不懂咱就学,记不住多看几遍(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

spring 定时任务的 执行时间设置规则【老记不住】

单纯针对时间的设置规则 org.springframework.scheduling.quartz.CronTriggerBean允许你更精确地控制任务的运行时间,只需要设置其cronExpression属性。 一个cronExpression表达式有至少6个(也可能是7个)由空格分隔的时间元素。从左至右,这些元素的定义如下: 1.秒(0–59) 2.分钟(0–59) 3.小时(0–23

Linux 命令总记不住?一招帮你搞定!

作者: 一去、二三里个人微信号: iwaleon微信公众号: 高效程序员 对于 Linux 用户来说,man 应该是最常用的命令之一了,它主要用于显示某个命令/实用程序的详细说明,通常被称为“手册页”。 虽然 man 很强大,但却时常会让人崩溃,满屏的选项和解释,简直是又臭又长。为了解决这一问题,tldr 应运而生了。 Life is short, You need tldr tldr 是

我记不住的du和sort命令

背景: 最近总想多记下一些东西,以免忘记和遗留,用的时候直接查询自己的博客即可,不用再漫无目的的去搜索和再验证。今天讲一下之前用的很频繁的命令以供大家参考和学习。  1.  du  ---- estimate file space usage 千万注意:  du 展示的是磁盘空间占用量ls 展示的是文件内容的大小  du一共有几个常用的参数 -a            //显示所有文件的

听说 docker 命令你还记不住

这是「进击的Coder」的第 455 篇技术分享 作者:王小伍 来源:赫连小伍 “ 阅读本文大概需要 10 分钟。 ” docker 作为轻量级的、高性能的沙箱容器,使用频率极高,功能非常强大。 强大的功能需要繁杂的命令来支撑,虽然 docker 命令很多,多的记不住。 好记性不如一个烂笔头,本文汇总了 docker 常用的命令,并对每个命令进行说明和举例,可以随用随取 镜像仓库用来保存镜像

我学啥你就学啥Docker(1)No.125

Docker安装 一般项目Dokcer 的安装比较简单,直接 brew install Docker  就可以了,不会安装的自己谷哥或者度娘去去去去。 容器历史 凡事先聊聊历史,其实在 Docker 出现之前,也出现过很多很多其他的容器技术,比如 chroot、 FreeBSD Jails、Linux VServer、 Solaris容器 等等。至于为什么会出现容器技术,大概是因为

我学啥你就学啥Kubernetes(0)No.119

好,现在开始跟大家一起学习  Kubernetes  ,本系列文章不会有什么安排,起点就是,我学什么我就向你们分享什么,期望大家对这门目前比较主流的技术有一定的了解,以及一定的动手能力,给我自己扫扫盲,也给大家扫扫盲。 主要知识来源: 极客时间专栏《深入剖析 Kubernetes》 书籍《Kubernetes权威指南》 官网 https://kubernetes.io Google Ku

php中$_SERVER['HTTP_HOST']和$_SERVER['SERVER_NAME']的区别,记不住来看看就知道了

$_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME']:$_SERVER['SERVER_PORT'] 当URL为:http://www.tomener.com/love.php $_SERVER['HTTP_HOST']输出是:www.tomener.com $_SERVER['SERVER_NAME']输出是:www.tomener.com 为什么

坚持记点东西年纪大了记不住了

第一次写博客,以后想必会坚持这样的一个习惯,学会把当天不懂的东西与学到的好的东西记录下来,不断进步。到了离校的季节,同学纷纷找到工作,而我却在苦逼的搞培训,嵌入式之路注定充满艰辛,我不会放弃自己,会加油! 既然选择了去培训,就不会辜负自己的选择,要用心的学点东西,为以后职场做好离校前最最好的准备,不再去想不相关的东西,一心一意去做,专注,注意细节,相信奋斗的力量,加油

听说docker命令你还记不住

docker作为轻量级的、高性能的沙箱容器,使用频率极高,功能非常强大。 强大的功能需要繁杂的命令来支撑,虽然docker命令很多,多的记不住。 好记性不如一个烂笔头,本文汇总了docker常用的命令,并对每个命令进行说明和举例,可以随用随取 镜像仓库用来保存镜像,可分为远程镜像仓库和本地镜像仓库。 通过pull命令可以把远程仓库的镜像下载到本地,通过push命令可以把本地仓库的镜像推