零碎的知识点-5

零碎的知识点-5


AOP的实现方式

AOP是Aspect Oriented Programming的缩写,意思是面向方面编程。

Java可以通过三个层面来实现AOP:

  1. 在编译期修改源代码
  2. 在运行期字节码加载前修改字节码 0
  3. 在运行期字节码加载后动态创建代理类的字节码

各种实现机制的比较:

类别机制原理优点缺点实现的代表
静态AOP静态织入在编译期,切面直接以字节码的形式编译到目标字节码文件中对系统无性能影响灵活性不够AspectJ
动态AOP动态代理在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中相对于静态AOP更加灵活切入的关注点需要实现接口。对系统有一点性能影响JDK动态代理,Spring
动态字节码生成CGLIB在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中 没有接口也可以织入 扩展类的实例方法为final时,则无法进行织入CGLIB,Spring
自定义类加载器在运行期,目标加载前,将切面逻辑加到目标字节码里可以对绝大部分类进行织入代码中如果使用了其他类加载器,则这些类将不会被织入
字节码转换在运行期,所有类加载器加载字节码前进行拦截可以对所有类进行织入

AOP里的成员:

  • Joinpoint:拦截点,如某个业务方法
  • Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint
  • Advice:要切入的逻辑
  • Before Advice:在方法前切入
  • After Advice:在方法后切入,抛出异常则不会切入
  • After Returning Advice:在方法返回后切入,抛出异常则不会切入
  • After Throwing Advice:在方法抛出异常时切入
  • Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行

AOP实现的功能

  • 性能监控:在方法调用前后记录调用时间,方法执行太长或超时报警
  • 缓存代理:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取
  • 软件破解:使用AOP修改软件的验证类的判断逻辑
    记录日志:在方法执行前后记录系统日志
  • 工作流系统:工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务
  • 权限验证:方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉

Spring的AOP

Spring默认采取动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用cglib机制

但Spring的AOP有一定的缺点:

  • 第一,只能对方法进行切入,不能对接口、字段、静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法都将被切入)
  • 第二,同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean
  • 第三,性能不是最好的。从前面几节得知,我们自定义的类加载器,性能优于动态代理和cglib

用双向链表实现一个栈

java里默认的LinkedList很适合包装成栈,因为栈不需要随机访问。

public class DLStack implements Stack<Integer> {

    private LinkedList<Integer> storage = new LinkedList<>();


    /**
     * 压出元素
     *
     * @param v 元素
     */
    @Override
    public void push(Integer v) {
        storage.addFirst(v);
    }

    /**
     * 获取栈顶元素,但并不将其从栈顶移除。
     *
     * @return 栈顶元素
     */
    @Override
    public Integer peek() {
        return storage.getFirst();
    }

    /**
     * 移除并返回栈顶元素
     *
     * @return 栈顶元素
     */
    @Override
    public Integer pop() {
        return storage.removeFirst();
    }

    /**
     * 判断栈是否为空
     *
     * @return 是否为空
     */
    @Override
    public boolean empty() {
        return storage.isEmpty();
    }

    /**
     * 获取栈的大小
     *
     * @return 栈的大小
     */
    @Override
    public long size() {
        return storage.size();
    }

   public String toString(){
        return storage.toString();
    }
}

countdownlatch和cyclicbarrier的作用和区别

countdownlatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。

他是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行

CountDownLatch的使用场景:

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

总结:CountDownLatch实质上就是一个AQS计数器,通过AQS来实现线程的等待与唤醒。

CyclicBarrier

CyclicBarrier,让一组线程到达一个同步点后再一起继续运行,在其中任意一个线程未达到同步点,其他到达的线程均会被阻塞。

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier 使用场景:

CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。

CyclicBarrier和CountDownLatch的区别

  1. 对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
  2. CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
  3. CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
    4, CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false。

Spring IOC循环依赖解决方案

循环依赖就是N个类中循环嵌套引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。下面说一下Spring是如果解决循环依赖的。

Spring为了解决单例的循环依赖问题,使用了三级缓存。

  • singletonFactories : 单例对象工厂的cache
  • earlySingletonObjects :提前暴光的单例对象的Cache
  • singletonObjects:单例对象的cache
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

所以,Spring容器循环依赖包括: 构造器循环依赖和setter循环依赖。

构造器参数循环依赖

通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖

原理:Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符创建过程中将一直保持在这个池中,因为如果在创建bean过程中发现自己已经在“当前创建bean池”中时,将会抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean将从“当前创建bean池”中清除掉。

setter方式单例,默认方式

Spring的单例对象的初始化主要分为三步:

Created with Raphaël 2.1.2 createBeanInstance实例化 populateBean填充属性 InitializeBean初始化
  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml等配置文件中的init 方法。

Spring是先将Bean对象实例化之后再设置对象属性的

原理:对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(比如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。

通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean addSingletonFactory()方法。Spring先是用构造实例化Bean对象 ,此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽、

setter方式原型,prototype

  1. 对于scope为prototype范围的bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean,所以检测到循环依赖会直接抛出BeanCurrentlyInCreationException异常

  2. 对于单例作用域的bean,可以通过setAllowCircularReferences(false)来禁用循环引用,这样如果存在循环引用就会抛出异常来通知用户。


利用classloader实现热部署

对于Java应用程序来说,热部署就是在运行时更新Java类文件。

为了实现热部署,可以自定义类加载器,并重写ClassLoader的findClass方法。想要实现热部署可以分以下三个步骤:
1、销毁该自定义ClassLoader
2、更新class类文件
3、创建新的ClassLoader去加载更新后的class类文件。


热部署与热加载

Java热部署与热加载的联系:

  1. 不重启服务器编译/部署项目
  2. 基于Java的类加载器实现

Java热部署与热加载的区别:

部署方式:

  • 热部署在服务器运行时重新部署项目
  • 热加载在运行时重新加载class

实现原理 :

  • 热部署直接重新加载整个应用
  • 热加载在运行时重新加载class

使用场景:

  • 热部署更多的是在生产环境使用
  • 热加载则更多的实在开发环境使用

Tomcat热部署机制

Tomcat的容器实现热部署使用了两种机制:

  1. Classloader重写,通过自定义classloader加载相应的jsp编译后的class到JVM中。 通过动态修改内存中的字节码,将修改过的class再次装载到JVM中。

Tomcat通过org.apache.jasper.servlet.JasperLoader实现了对jsp的加载。

  1. 通过代理修改内存中class的字节码

Tomcat中的class文件是通过org.apache.catalina.loader. WebappClassLoader装载的。在热部署的情况下,对于被该classloader 加载的class文件,它的classloader始终是同一个WebappClassLoader,除非容器重启了。

HashMap的一些知识点:

如何处理null

当key为null时,HashMap执行putForNullKey()方法。在方法里有个for循环,是在talbe[0]链表中查找key为null的元素,如果找到,则将value重新赋值给这个元素的value,并返回原来的value。
如果上面for循环没找到则将这个元素添加到talbe[0]链表的表头。

HashMap在JDK1.7与JDK1.8的区别

JDK1.7中

使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个桶里,这些key会形成一个链表。

在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表,也就是说时间复杂度在最差情况下会退化到O(n)

DK1.8中

使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构。如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个桶里。如果同一个桶里的key不超过8个,使用链表结构存储。如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销,也就是说put/get的操作的时间复杂度最差只有O(log n)。

但是真正想要利用JDK1.8的好处,有一个限制:key的对象,必须正确的实现了Compare接口。如果没有实现Compare接口,或者实现得不正确(比方说所有Compare方法都返回0),那JDK1.8的HashMap其实还是慢于JDK1.7的。

解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。


B树与B+树简要区别

B树

每个节点都储存key和data,所有节点组成这棵树,并且叶子节点指针为null。

B+树

只有叶子节点存储data,叶子节点包含这棵树的所有键值,字节节点不储存指针。
后来,在B+树上增加了顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针,这样一棵树成了数据库系统实现索引的首选数据结构。

因为B+树内节点不存储data,这样一个节点就可以存储更多的key,更适合做数据库的索引。

MyISAM

data存的是数据地址。索引是索引,数据是数据。

InnoDB

data存的是数据本身。索引也是数据。


MyISAM和InnoDB的主要区别和应用场景

主要区别:

  1. MyISAM是非事务安全型的,而InnoDB是事务安全型的。
  2. MyISAM锁的粒度是表级,而InnoDB支持行级锁定。
  3. MyISAM支持全文类型索引,而InnoDB不支持全文索引。(MySQL 5.7开始支持全文索引)
  4. MyISAM相对简单,所以在效率上要优于InnoDB,小型应用可以考虑使用MyISAM。
  5. MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦。
  6. InnoDB表比MyISAM表更安全,可以在保证数据不会丢失的情况下,切换非事务表到事务表(alter table tablename type=innodb)。

应用场景:
1. MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
2. InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。


拦截器和过滤器的区别

过滤器:

依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。

拦截器:

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理

主要区别:

  1. 拦截器interceptor是基于Java的反射机制的,而过滤器Filter是基于函数回调,实现的filter接口中doFilter方法就是回调函数。
  2. 拦截器interceptor不依赖与servlet容器,过滤器Filter依赖与servlet容器,没有servlet容器就无法来回调doFilter方法。
  3. 拦截器interceptor只能对action请求起作用,而过滤器Filter则可以对几乎所有的请求起作用,Filter的过滤范围比Interceptor大,Filter除了过滤请求外通过通配符可以保护页面,图片,文件等等。
  4. 拦截器interceptor可以访问action上下文、值栈里的对象,而过滤器Filter不能访问。
  5. 在action的生命周期中,拦截器interceptor可以多次被调用,而过滤器Filter只能在容器初始化时被调用一次。
  6. 拦截器interceptor可以获取IOC容器中的各个bean,而过滤器Filter就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

CMS收集器和G1收集器优缺点与区别

CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,CMS收集器是基于“”标记–清理”算法实现的,整个过程分为四个步骤:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清理

优点:并发收集,低停顿

缺点:

  1. CMS收集器对CPU资源非常敏感
    1. CMS处理器无法处理浮动垃圾(即标记过程产生的垃圾)
    2. CMS是基于“标记–清除”算法实现的,所以在收集结束的时候会有大量的空间碎片产生。

G1收集器

Java8之后广泛使用,G1 将整个对区域划分为若干个Region,每个Region的大小是2的倍数(1M,2M,4M,8M,16M,32M,通过设置堆的大小和Region数量计算得出。
Region区域划分与其他收集类似,不同的是单独将大对象分配到了单独的region中,会分配一组连续的Region区域(Humongous start 和 humonous Contoinue 组成),所以一共有四类Region(Eden,Survior,Humongous和Old),
G1 作用于整个堆内存区域,设计的目的就是减少Full GC的产生。在Full GC过程中由于G1 是单线程进行,会产生较长时间的停顿。
G1的OldGc标记过程可以和yongGc并行执行,但是OldGc一定在YongGc之后执行,即MixedGc在yongGC之后执行。

名词解释(JDK1.8):

  • MetaSpace
    在Java8之后取代永久代方法区的内存部分,NativeMemory
  • Mixed GC Event
    所有Young Region和一部分Old Region的混合GC时间。
  • Reclaimable
    G1 为了能够回收,创建了一系列专门用于对象回收的Region,存放在链表中,只包含存活率小于-XX:G1MixedGCLIveThresholdPercent(默认是85%)的region, region的值除以整个Java堆区,如果大于-XX:G1HeapWastePercent(默认5%),则启动回收机制
  • Minor GC
    年轻代GC
  • Major GC(Full GC)
    发生在老年代的GC

G1是一款面向服务端应用的垃圾收集器。G1具备如下特点:

  1. 并行于并发
  2. 分代收集
  3. 空间整合(,G1从整体来看是基于“标记整理”算法实现的收集器,从局部上来看是基于“复制”算法实现的。)
  4. 可预测的停顿

G1运行步骤:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收

G1相对于CMS的主要区别在:

  1. G1在压缩空间方面有优势
  2. G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
  3. Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活
  4. G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象
  5. G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做
  6. G1会在Young GC中使用、而CMS只能在O区使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值