杂谈

328 阅读7分钟

序列化方式:

jdk序列化、json、xml、hessian、photobuf,评价序列化好坏的标准是,序列化后文件大小,序列化速度。

ThreadLocal

ThreadLocal 保证了每个线程都有自己的ThreadLocal数据副本,每个线程中都维护了一个ThreadLocal的ThreadLocalMap的内部类,里面封装了一个entry数组,entry的key是 ThreadLocal对象,value是具体的值。entry数组采取开放地址法解决hash冲突,如果发生hash冲突,则判断下一个位置是否为空,知道找到空位置。删除的时候,需要将删除元素后面 的元素进行重新rehash的过程,不然get的时候,get不到对应的值,不知道元素是被删了,还是不存在,这是开发地址法带来的弊端。使用ThreadLocal要注意内存泄漏,使用完毕, 要调用remove方法,把entry中的ThreadLocal和value都置为空。由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长, 如果都没有手动删除对应key,都会导致内存泄漏。但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(), remove()的时候会被清除。因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏, 而不是因为弱引用。

AQS

AQS是JUC包下提供的一个抽象类,是实现ReentrantLoct、countDownLacth等并发工具的基础,它内部主要维护着一个volitie修饰的资源对象和一个发生资源竞争时的node等待对列, node里封装了线程和等待状态等等一些信息。AQS定义实现了线程的入队和唤醒操作,主要是通过cas、for死循环和LockSuport来实现。继承AQS的类,不需要重新维护一套获取资源失败 入队、唤醒出队等逻辑,只需重写AQS中的tryAcquire和tryRelease,如果是共享方式的情况,需要重新tryAcquireShare和tryReleaseShare。这几个方法控制了获取资源的逻辑,比如 是公平锁和非公平锁的区别实际就是tryAcquire的区别。ReentrantLoct中的非公平锁就在tryAcquire在state为0的时候,不管等待对列中有没有线程等待,直接设置当前线程为自己 实现插队获取,公平锁在state为0时,要判断是否有等待线程,有的话要入队。

AQS中的lock等待过程中,不响应中断,等获取到锁之后再响应中断。如果在等待过程中发生了中断,会把中断标记变量记为true,等到线程获取锁的时候,返回中断标记变量,在acquire()中使用 selfInterrupt响应中断,需要注意的是 LockSupport.park(),在发生中断的时候会返回,但是不会清除线程的中断标记位,所以在等待过程中发生中断的时候,park返回 并调用Thread.interrupted(),返回中断标记,并清除线程的中断标记位,如果不这样做,那么下次,循环到 LockSupport.park()时,由于当前线程的中断标记为true, 不会进行阻塞,直接返回,这样就会造成阻塞失败,造成cpu空转,使用率飙升。

tryLock(time)和acquireInterruptibly可以在等待的过程中响应中断,通过判断线程中断标记位,直接抛出中断异常。

sql调优过程:

首先发现需要优化的sql,方式:接口响应速度变慢,慢查询日志。优化sql:使用explan查看执行计划,查看sql有没有走索引,哪一步耗时比较多,每个步骤扫描的数据量, 是否存在子查询等等。如果存在子查询,则考虑使用join优化子查。如果没有索引,则考虑关键字段添加索引,如果有索引没有走索引,则分析索引失效原因。 如果走了索引还是很慢,则考虑尽量将sql拆分成简单的sql,让大表查询尽量简单化,通过主键多次查询,在java程序中拼接结果。如果大表的查询出来的结果集还是很多,可以通过 将结果集插入中间表,在中间表中建立索引进行优化。也可以考虑通过分区分表的方式解决问题。

分库分表相关:

分库分表可以解决单表数据量过大带来的查询和插入更新问题,缓解数据库高并发访问瓶颈。 分表键的选择: 1、首先考虑业务场景,比如侧重客户维度的业务场景,使用客户号作为分表键,侧重商家维度的业务使用商户号作为分表键,如果没有侧重点,就使用主键 2、其次分布式主键的生成可以加入业务含义

分表后的查询:

1、建立索引表,比如订单的分表键为主键,通过客户维度查询的话,在插入订单表的时候,在索引表中也插入客户与订单关联的索引数据,这个索引表可以通过客户维度进行分表。 2、跨库join的情况,查询频率不高的情况下可以用程序进行聚合,频率高的话通过3、4 3、通过ES查找 4、数据同步

分库后的扩容:

一开始分库的时候就使用上一致性hash的方式

Spring Aop 代理:

静态代理:编译期织入,编译后织入,类加载期织入(需要引入特殊编译期比如aspectj),自己编写代码

动态代理:jdk动态代理,cglib动态代理,javasisst

动态代理原理:

jdk动调代理,在运行时生成传入目标类的接口,和invokectionHander,使用proxy类动态生成代理类的class对象,class对象为$Proxy0 extends Proxy implements Subject,调用过程主要是依赖于反射机制。

cglib动态代理,运行时传入目标类类对象和MethodInterceptor ,使用Enhancer 动态生成代理类class对象, class对象为StudentEnhancerByCGLIBEnhancerByCGLIB1857f4b9 extends Student implements Factory,调用过程使用asm三方组件,编辑底层字节码结合拦截器生成目标类子类。

性能:创建效率cglib<jdk,运行效率cglib>jdk,因为创建过程cglib通过编辑字节码生成代理类对象,速度要比jdk生成那种方式要慢,但是调用过程中,cglib效率要比jdk快,因为反射调用比直接调用慢。

concurrenthashmap

扩容时读为什么是线程安全的:因为concurrenthashmap中数组的node里的key和next都是用volatile修饰的,保证了修改时的可见性,同时在访问时如果node的hash值是小于0,代表正在扩容,当前节点是扩容节点,就会调用ForwardingNode中的find函数定位到新数组上进行查找,ForwardingNode保存了新数组的引用。

put过程: 首先计算出key的hashcode,根据hashcode定位到在数组中的存放位置,然后判断当前位置有无节点,如果没有节点,通过cas的方式设置为当前节点,如果cas失败,证明当前发生了竞争,则通过for循环重新执行,如果判断当前位置的hash值为-1,则表明当前节点正在扩容,则进入帮助扩容阶段,如果hash值大于0且已经有节点存在,则对当前节点使用synchronize加锁处理,然后遍历链表加入新节点,如果链表长度超过8,则转为红黑树,添加完之后,会判断当前节点数是否达到扩容阈值(初始值*0.75),如果达到,则进入扩容流程。

关键属性含义:

LOAD_FACTOR :负载因子,默认0.75

sizeCtl: 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义

当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容

当为0时(默认值):代表当时的table还没有被初始化

当为正数时:表示初始化或者下一次进行扩容的大小