/javalearning

Java Learning & Practice

Primary LanguageJava

Java Learning


1. Design Pattern

1.1 Factory desgin

1.2 Singleton desgin

1.3 Decorator

1.4 Adapter

装饰器与适配器模式都有一个别名叫包装模式(Wrapper),它们的作用看似都是起到包装一个类或对象的作用, 但是使用它们的目的不一样。 适配器模式的意义是将一个接口转变成另一个接口,通过改变接口达到重复使用的目的; 而装饰器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但增强原有对象的功能, 或者改变原有对象的处理方法而提高性能。

1.5 Proxy

public static Object newProxyInstance(ClassLoader loader,
                  Class<?>[] interfaces,
                      InvocationHandler h)

JDK动态代理,创建一个com.sun.proxy.$Proxy0类,继承Proxy,动态实现interfaces接口,通过父类Proxy构造器 Constructor(InvocationHandler h),实例化一个interfaces类型的对象,最后通过h.invoke反射调用对应的方法。


代理方式 实现 优点 缺点 特点
JDK动态代理 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler,并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 不需要硬编码接口,代码复用率高 只能够代理实现了接口的委托类 底层使用反射机制进行方法的调用
CGLIB动态代理 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法, 一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 不能对final类以及final方法进行代理 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

Cglib

    //final修饰的类和方法不能被继承和修改
    public class UserLog$$EnhancerByCGLIB$$9a9593ca extends UserLog implements Factory{
        //...
        final void CGLIB$doLog$0() {
            super.doLog();
        }
    
        public final void doLog() {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }
    
            if (var10000 != null) {
                var10000.intercept(this, CGLIB$doLog$0$Method, CGLIB$emptyArgs, CGLIB$doLog$0$Proxy);
            } else {
                super.doLog();
            }
        }
        //...
    }
    
     private static class FastClassInfo{
            FastClass f1; // org.lynn.designPattern.proxy.cglib.UserLog的fastclass
            FastClass f2; // UserLog$$EnhancerByCGLIB$$9a9593ca 的fastclass
            int i1; //方法doLog在f1中的索引
            int i2; //方法CGLIB$doLog$0在f2中的索引
     }
     
     public class MethodProxy {
         //...
         public Object invokeSuper(Object obj, Object[] args) throws Throwable {
             try {
                 this.init();
                 MethodProxy.FastClassInfo fci = this.fastClassInfo;
                 return fci.f2.invoke(fci.i2, obj, args);
             } catch (InvocationTargetException var4) {
                 throw var4.getTargetException();
             }
         }
         //...
     }
     //使用proxy.invokeSuper(obj,args)方法,就是执行原始类的方法。
     // 还有一个方法proxy.invoke(obj,args),这是执行生成子类的方法。
     // 如果传入的obj就是子类的话,会发生内存溢出,因为子类的方法不挺地进入intercept方法,
     // 而这个方法又去调用子类的方法,两个方法直接循环调用了。
cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

1.6 Strategy

1.7 TemplateMethod

模板方法设计模式

1.7 SnowFlake

Twitter分布式自增id算法(64位)

正数 当前时间戳 - 开始时间戳(41位) 数据中心标识(5位) 机器标识(5位) 自增序列(12位)
0 0000000000 0000000000 0000000000 0000000000 0 00000 00000 000000000000

2. Framework

2.1 plugin chain simple implement

根据mybatis简单实现的一个pluginChain

简陋版,会针对所有的方法进行拦截

2.2 spring mvc 1.0

简单实现springMVC定位、加载、注册过程

2.3 filters

通过匿名内部类创建FilterChain,并对匿名内部类有个深入的理解

外部类方法中传入匿名内部类的变量,匿名内部类实际上持有了该变量的一个拷贝,如果对此拷贝进行改变, 不会反应到方法中,而对于开发者而言,看到的是同一个对象,所以不能保持同步修改,故方法中的变量需要定义为final.

2.4 spring mvc 2.0

实现springMVC定位、加载、注册过程

实现handlerAdapter,HandlerMapping,Aop,DispatchServlet...

可以通过url进行访问

2.5 aop拦截

  protected Object invokeJoinpoint() throws Throwable {
      return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
  }

aop对Service类中的a() b()方法均配置的拦截,当a()内部调用b(),无法做到对b的拦截 invokeJoinpoint()中传递的对象为目标对象,而不是被aop代理过的对象,即this.target 调用b()方法时,并没有过代理对象来执行,所以无法拦截。如果需要在a()方法中调用b()方法,并且对b()进行拦截, 则需要获取到Service类的代理对象来进行调用,((Service)AopContext.currentProxy()) -> b()

<aop:config expose-proxy="true" proxy-target-class="false"/> 需要配置此项,暴露代理对象,实现线程内共享,使用ThreadLocal模式

AbstractAutoProxyCreator实现了BeanPostProcessor接口,spring容器初始化bean后,调用postProcessAfterInitialization 对bean进行wrapIfNecessary,创建一个aop的proxy对象。

   ProxyFactory -> aopProxy -> getProxy
                                <- JDKDynamicAopProxy
                                <- CglibAopProxy
   ProxyFactory保存了aop拦截的配置信息                    

AbstractAutoProxyCreator实现了Ordered接口,并将期顺序设置为Ordered.LOWEST_PRECEDENCE,最低优先级 保证aop后置处理器最后调用

2.6 Bean的初始化扩展方法

init-method,afterPropertiesSet和BeanPostProcessor

  1. 先执行类的构造器,进行实例化
  2. 接着执行 BeanPostProcessor -> postProcessBeforeInitialization
  3. 然后到InitializingBean -> afterPropertiesSet
  4. 再到配置的init-method 方法
  5. 最后 BeanPostProcessor -> postProcessAfterInitialization
  • init-method和afterPropertiesSet可以针对某个单独的bean进行处理, BeanPostProcessor可以针对容器中所有的bean进行处理

  • 如果一个Bbean是lazy-init,而另一个none lazy-init的singleton Abean依赖于它, 那么当ApplicationContext实例化singleton Abean时, 必须确保上述singleton Abean所依赖所有bean也被预先初始化,包括设置为lazy-init的Bbean, 这种情况也符合延时加载的bean在第一次调用时才被实例化的规则。

2.7 Spring Annotation

@Component 注解的“派生”

@Controller @Service @Repository均为@Component注解的派生,类似注解的继承关系

@Resource & @Autowire

  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;

  • @Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用;

  • @Resource注解是又J2EE提供,而@Autowired是由Spring提供,故减少系统对spring的依赖建议使用@Resource的方式;

  • @Resource和@Autowired都可以书写标注在字段或者该字段的setter方法之上

2.8 Spring MVC

  • 通过 org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener 监听ContextRefreshedEvent 调用 org.springframework.web.servlet.DispatcherServlet#onRefresh 初始化以下内容
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
  • HandlerMapping 定位
    • 遍历容器中所有的bean,(isHandler())找到@Controller 或者 @RequestMapping 注解的bean,处理成HandlerMethod,注册到mappingRegistry
    • HandlerAdapter,为了适配handler并统一返回ModelAndView对象,通过HandlerAdapter调用HandlerMethod
  ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
  • mappingRegistry
    • registry(HashMap)
      • <RequestMappingInfo, MappingRegistration>
        • MappingRegistration
          • mapping
          • handlerMethod
    • mappingLookup
    • urlLookup
    • nameLookup

2.9 Spring Transaction

  • @Configuration ProxyTransactionManagementConfiguration
    • 自动装配配置类 ProxyTransactionManagementConfiguration,这个类首先注入了AnnotationTransactionAttributeSource,用来读取解析 @Transactional注解,获取需要进行事务管理的方法,并将相关的事务管理配置的参数暴露给Spring。
    • 注入TransactionInterceptor:基于AOP MethodInterceptor (Advice)实现的声明式事务管理,内部依赖于TransactionManager,TransactionManager是实际的事务管理对象。
    • 注入BeanFactoryTransactionAttributeSourceAdvisor:由AnnotationTransactionAttributeSource驱动的AOP Advisor,用于为@Transactional注解的方法添加一个事务advice通知
  • 解析标签 <tx:annotation-driven/>,org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer#configureAutoProxyCreatorAnnotationTransactionAttributeSourceBeanFactoryTransactionAttributeSourceAdvisor解析为BeanDefinition注册到容器中,初始化BeanFactoryTransactionAttributeSourceAdvisor时,会将AnnotationTransactionAttributeSource初始化并进行注入

3. Concurrent

3.1 先++ 后++

  • 先++:先运算,后使用

  • 后++:先使用,后运算

    String[] names = {"jack","tom","lily"};
    int index = 0;
    System.out.println(names[index ++]);//输出jack
    System.out.println(names[index]);//输出tom
    System.out.println(names[++index]);//输出lily

3.2 Lock

  • 自旋锁:

    一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。 自旋锁适用于锁使用者保持锁时间比较短的情况

  • 互斥锁:

    目的和自旋锁一样,但机制不一样,当线程占用资源后,加上锁,后者线程访问时,由于资源被占有,转入休眠(sleep)状态,等资源被释放后,通过信号量通知排队等候的线程。

    自旋锁是指锁的实现方式
    互斥锁是指锁的类型,互斥锁可以用自旋来实现
    
  • 可重入锁

同一个线程,外层方法获取锁后,内层方法仍有获得该锁的代码,不受影响。

如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,
而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放。

3.3 JVM内置锁的膨胀

简书:浅谈偏向锁、轻量级锁、重量级锁

CSDN:java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁

偏向锁 -> 轻量级锁 -> 重量级锁  
  • JDK1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,这些都属于乐观锁
  • JDK1.6引入自适应自旋锁, -XX:+UseSpinning开启; -XX:PreBlockSpin=10 为自旋次数;
  • JDK1.7后,去掉此参数,由jvm控制;

mark word

状态 标志位 存储内容
未锁定 01 对象哈希码、对象分代年龄,是否可偏向锁0
偏向锁 01 偏向线程ID、偏向时间戳、对象分代年龄,是否可偏向锁1
轻量级锁定 00 指向锁记录的指针
膨胀(重量级锁定) 10 执行重量级锁定的指针
GC标记 11 空(不需要记录信息)

偏向锁: 顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况, 则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会撤销它身上的偏向锁,此时会发生stop the world, 锁升级为轻量级锁

轻量级锁: 轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。

JAVA锁的膨胀过程和优化

  • 加锁的过程:JVM在当前线程的栈帧中创建用于储存锁记录的空间(LockRecord),然后把MarkWord放进去,同时生成一个叫Owner的指针指向那个被加锁的对象,用CAS尝试把对象头的MarkWord替换成一个指向锁记录(LockRecord)的指针。 成功了就拿到了锁。那么失败了呢? 《深入理解JVM》的说法:失败了,去查看MarkWord的值。有2种可能:1,指向当前线程的指针,2,别的值。

    • 如果是1,那么说明发生了“重入”的情况,直接当做成功获得锁处理。其实这个有个疑问,为什么获得锁成功了而CAS失败了,这里其实要牵扯到CAS的具体过程:先比较某个值是不是预测的值,是的话就动用原子操作交换(或赋值),否则不操作直接返回失败。在用CAS的时候期待的值是其原本的MarkWord。 发生“重入”的时候会发现其值不是期待的原本的MarkWord,而是一个指针,所以当然就返回失败,但是如果这个指针指向这个线程,那么说明其实已经获得了锁,不过是再进入一次。

    • 如果是2,那么发生了竞争,锁会膨胀为一个重量级锁(MutexLock)

    《并发编程的艺术》的说法:失败了直接自旋。期望在自旋的时间内获得锁,如果还是不能获得,那么开始膨胀,修改锁的MarkWord改为重量级锁的指针,并且阻塞自己。

  • 解锁过程:当前持有锁的线程,用CAS把MarkWord换回到原来的对象头,如果成功,那么没有竞争发生,解锁完成。 如果失败,表示存在竞争(之前有线程试图通过CAS修改MarkWord,即MarkWord被修改为了重量级锁),这时要释放锁并且唤醒阻塞的线程。

重量级锁: 重量级锁在轻量级锁自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时, 轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

3.4 锁优化

  • 减少锁的时间

不需要同步执行的代码,能不放在同步快里面执行就不要放在同步快内,可以让锁尽快释放;

  • 减少锁的粒度

它的**是将物理上的一个锁,拆成逻辑上的多个锁,增加并行度,从而降低锁竞争。它的**也是用空间来换时间;

ConcurrentHashMap

java中的ConcurrentHashMap在jdk1.8之前的版本,使用一个Segment 数组Segment< K,V >[] segments; Segment继承自ReenTrantLock,所以每个Segment就是个可重入锁,每个Segment 有一个HashEntry< K,V >数组用来存放数据,put操作时,先确定往哪个Segment放数据,只需要锁定这个Segment,执行put,其它的Segment不会被锁定;所以数组中有多少个Segment就允许同一时刻多少个线程存放数据,这样增加了并发能力。

LongAdder

LongAdder 实现思路也类似ConcurrentHashMap,LongAdder有一个根据当前并发状况动态改变的Cell数组,Cell对象里面有一个long类型的value用来存储值; 开始没有并发争用的时候或者是cells数组正在初始化的时候,会使用cas来将值累加到成员变量的base上,在并发争用的情况下,LongAdder会初始化cells数组,在Cell数组中选定一个Cell加锁,数组有多少个cell,就允许同时有多少线程进行修改,最后将数组中每个Cell中的value相加,在加上base的值,就是最终的值;cell数组还能根据当前线程争用情况进行扩容,初始长度为2,每次扩容会增长一倍,直到扩容到大于等于cpu数量就不再扩容,这也就是为什么LongAdder比cas和AtomicInteger效率要高的原因,后面两者都是volatile+cas实现的,他们的竞争维度是1,LongAdder的竞争维度为“Cell个数+1”为什么要+1?因为它还有一个base,如果竞争不到锁还会尝试将数值加到base上;

LinkedBlockingQueue

LinkedBlockingQueue也体现了这样的**,在队列头入队,在队列尾出队,入队和出队使用不同的锁,相对于LinkedBlockingArray只有一个锁效率要高;

拆锁的粒度不能无限拆,最多可以将一个锁拆为当前cup数量个锁即可;

  • 锁粗化

大部分情况下我们是要让锁的粒度最小化,锁的粗化则是要增大锁的粒度; 在以下场景下需要粗化锁的粒度:
假如有一个循环,循环内的操作需要加锁,我们应该把锁放到循环外面,否则每次进出循环,都进出一次临界区,效率是非常差的;

  • 使用读写锁

ReentrantReadWriteLock 是一个读写锁,读操作加读锁,可以并发读,写操作使用写锁,只能单线程写;

  • 读写分离

CopyOnWriteArrayList 、CopyOnWriteArraySet CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy, 复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的**, 读和写不同的容器。CopyOnWrite并发容器用于读多写少的并发场景,因为,读的时候没有锁, 但是对其进行更改的时候是会加锁的,否则会导致多个线程同时复制出多个副本,各自修改各自的;

  • 使用cas

如果需要同步的操作执行速度非常快,并且线程竞争并不激烈,这时候使用cas效率会更高, 因为加锁会导致线程的上下文切换,如果上下文切换的耗时比同步操作本身更耗时,且线程对资源的竞争不激烈, 使用volatile+cas操作会是非常高效的选择;

  • 消除缓存行的伪共享

除了我们在代码中使用的同步锁和jvm自己内置的同步锁外,还有一种隐藏的锁就是缓存行,它也被称为性能杀手。 在多核cup的处理器中,每个cup都有自己独占的一级缓存、二级缓存,甚至还有一个共享的三级缓存,为了提高性能, cpu读写数据是以缓存行为最小单元读写的;32位的cpu缓存行为32字节,64位cup的缓存行为64字节,这就导致了一些问题。 例如,多个不需要同步的变量因为存储在连续的32字节或64字节里面,当需要其中的一个变量时, 就将它们作为一个缓存行一起加载到某个cup-1私有的缓存中(虽然只需要一个变量,但是cpu读取会以缓存行为最小单位, 将其相邻的变量一起读入),被读入cpu缓存的变量相当于是对主内存变量的一个拷贝,也相当于变相的将在同一个缓存行中的几个变量加了一把锁, 这个缓存行中任何一个变量发生了变化,当cup-2需要读取这个缓存行时, 就需要先将cup-1中被改变了的整个缓存行更新回主存(即使其它变量没有更改), 然后cup-2才能够读取,而cup-2可能需要更改这个缓存行的变量与cpu-1已经更改的缓存行中的变量是不一样的, 所以这相当于给几个毫不相关的变量加了一把同步锁; 为了防止伪共享,不同jdk版本实现方式是不一样的:

  1. 在jdk1.7之前会 将需要独占缓存行的变量前后添加一组long类型的变量,依靠这些无意义的数组的填充做到一个变量自己独占一个缓存行;
  2. 在jdk1.7因为jvm会将这些没有用到的变量优化掉,所以采用继承一个声明了好多long变量的类的方式来实现;
  3. 在jdk1.8中通过添加sun.misc.Contended注解来解决这个问题,若要使该注解有效必须在jvm中添加以下参数: -XX:-RestrictContended

4.MySql

4.1 B-tree & B+tree

漫画B+树

B+树和B树相比,主要的不同点在以下3项:

  • 内部节点中,关键字的个数与其子树的个数相同,不像B树,子树的个数总比关键字个数多1个。
  • 所有指向文件的关键字及其指针都在叶子节点中,不像B树,有的指向文件的关键字是在内部节点中。 换句话说,B+树中,内部节点仅仅起到索引的作用。
  • 在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而是继续向下搜索这个分支。

B+tree 优势

B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。
因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,
那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。
相对来说I/O读写次数也就降低了。

B+树的查询效率更加稳定

由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。
所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,
导致每一个数据的查询效率相当。

B+树更有利于对数据库的扫描

B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,
而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,
所以对于数据库中频繁使用的range query,B+树有着更高的性能。

4.2 MySql

1. MyISAM (非聚集索引)

  • MyISAM引擎使用B+Tree作为索引结构,叶结点的data域存放的是数据记录的地址

  • MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址

  • 主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。

2. InnoDB (聚集索引)

  • InnoDB的数据文件本身就是索引文件,InnoDB要求表必须有主键(MyISAM可以没有)

  • InnoDB的辅助索引data域存储相应记录主键的值而不是地址, 辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

3. MySql Lock & Transaction

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同, 后者是通过在数据块中,对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着: 只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁,在实际开发中应当注意。

共享锁又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

排他锁又称为写锁,简称X锁,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁, 其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

4.事务(原子性、一致性、隔离性、持久性)隔离级别

  1. 脏读:在一个事务处理过程里读取了另一个未提交的事务中的数据。

  2. 不可重复读:指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值, 这是由于在查询间隔,被另一个事务修改并提交了。

  3. 幻读:事务非独立执行时发生的一种现象。 例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作, 这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。 而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改, 其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。(Mysql默认隔离级别)

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

5. JDK

5.1 ConcurrentHashMap

1.7 采用(Segment extends ReentrantLock)[] + HashEntry[] 的分段锁技术来实现同步

1.8 采用Synchronized & CAS 来实现并发同步,并且使用与HashMap相同的数据结构 Node数组 + 链表 + 红黑树

5.2 Throwable

Error 和 Exception均继承自Throwable

  • Error : 程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。

  • Exception : 程序本身可以处理的异常。

    • Unchecked Exception:

      • 指的是程序的瑕疵或逻辑错误,并且在运行时无法恢复。
      • 包括Error与RuntimeException及其子类,如:OutOfMemoryError, UndeclaredThrowableException, IllegalArgumentException, IllegalMonitorStateException, NullPointerException, IllegalStateException, IndexOutOfBoundsException等。
      • 语法上不需要声明抛出异常。
    • Checked Exception:

      • 代表程序不能直接控制的无效外界情况(如用户输入,数据库问题,网络异常,文件丢失等)
      • 除了Error和RuntimeException及其子类之外,如:ClassNotFoundException, NamingException, ServletException, SQLException, IOException等。
      • 需要try catch处理或throws声明抛出异常。

5.3 equals & ==

  • 对于基本数据类型,“==”比较的是两者的值是否相等。

  • 对于引用数据类型

    • “==”比较的是引用的地址是否相同(即是否是堆内的同一个对象);Object中的.equals()方法和"==’功能一样

    • String类中的.equals()方法重写了,比较的是两个引用对象的内容是否想同(即是否是完全相同的汽车(注意,有两辆汽车,且一模一样,完全相同))。

5.4 object.wait() && Thread.sleep()

  • wait只能在同步(synchronize)环境中被调用,而sleep不需要。
  • 进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。
  • wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真。但是sleep仅仅让你的线程进入睡眠状态。
  • wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
  • wait方法是针对一个被同步代码块加锁的对象,而sleep是针对一个线程。

5.5 Collection & Map

Collection:单列集合的根接口

  • List:元素有序  可重复 
    • ArrayList:类似一个长度可变的数组 。适合查询,不适合增删
      • ArrayList实现了RandomAccess接口,使用for循环遍历比iterator效率高
    • LinkedList:底层是双向循环链表。适合增删,不适合查询。
      • 遍历效率与linkedlist相反,使用iterator效率高
  • Set:元素无序,不可重复
    • HashSet:根据对象的哈希值确定元素在集合中的位置
    • TreeSet: 以二叉树的方式存储元素,实现了对集合中的元素排序

Map:双列集合的根接口,用于存储具有键(key)、值(value)映射关系的元素。

  • HashMap:用于存储键值映射关系,不能出现重复的键key,key和value允许null
  • TreeMap:用来存储键值映射关系,不能出现重复的键key,所有的键按照二叉树的方式排列
Hashtable HashMap
方法是同步的 方法是非同步的
基于Dictionary类 基于AbstractMap,而AbstractMap基于Map接口的实现
key和value都不允许为null,遇到null,直接返回 NullPointerException key和value都允许为null,遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理
hash数组默认大小是11,扩充方式是old*2+1 hash数组的默认大小是16,而且一定是2的指数
  • hash冲突
    • 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
    • 再哈希法
    • 链地址法(hashmap)
    • 公共溢出区

5.6 浅拷贝(ShallowClone) & 深拷贝(DeepClone)

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。 如果属性是基本类型,拷贝的就是基本类型的值; 如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。

  • 通过序列化、反序列化方式实现对象的深拷贝
  • transient属性无法实现拷贝

5.7 CountDownLatch

  • 通过内部类Sync extends AbstractQueueSynchronizer共享锁实现
  • countDownLatch.await(),进入线程等待队列,假设t1,t2进入
    • head -> t1 -> t2(tail)
  • countDown() 将 state-1,直到state=0时,准备释放共享锁,唤醒head节点next节点中的线程,AbstractQueuedSynchronizer#releaseShared() -> AbstractQueuedSynchronizer#doReleaseShared()->unparkSuccessor(),将t1设置为队列head节点,并将头节点thread设置为null,唤醒t1,t2.await()自旋获取共享锁成功后,head.next.thread.unpark()唤醒线程t2

5.8 CyclicBarrier

  • 通过ReentrantLock排他锁 & Condition实现
  • CyclicBarrier.await(),进入Condition条件队列,线程阻塞
  • 等到所有线程都到达屏障,count为0,Condition.signalAll
        //java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //创建新节点,添加到条件队列
            Node node = addConditionWaiter();
            //释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //如果不是在获取锁的阻塞队列中,阻塞当前线程
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //尝试获取锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

5.9位运算

  • 与(&):全1出1
  • 或(|):有1出1
  • 异或(^):值不同为1
    int i = 11;
    System.out.println(Integer.toBinaryString(11));
    System.out.println(Integer.toBinaryString(7));
    //11对8取余数,只需要跟( 11 & 2^3-1) 与运算即可
    System.out.println(11 & 7);
    //获取2^3-1位运算
    System.out.println(~(-1 << 3));
    

5.10 Executors and ThreadPoolExecutor

ThreadPoolExecutor构造参数

  • corePoolSize:核心线程数,如果运行的线程少于corePoolSize,则创建新线程来执行新任务。 corePoolSize <运行的线程数< maximumPoolSize:仅当队列满时才创建新线程 corePoolSize=运行的线程数= maximumPoolSize:创建固定大小的线程池

在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程

  • maximumPoolSize:最大线程数,可允许创建的线程数,corePoolSize和maximumPoolSize设置的边界自动调整池大小

  • keepAliveTime:如果线程数多于corePoolSize,则这些多余的线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize。

  • unit:keepAliveTime参数的时间单位

  • workQueue:保存任务的阻塞队列,与线程池的大小有关: 当运行的线程数少于corePoolSize时,在有新任务时直接创建新线程来执行任务而无需再进队列 当运行的线程数等于或多于corePoolSize,在有新任务添加时则选加入队列,不直接创建线程 当队列满时,在有新任务时就创建新线程

  • threadFactory:使用ThreadFactory创建新线程,默认使用defaultThreadFactory创建线程

  • handle:定义处理被拒绝任务的策略,默认使用ThreadPoolExecutor.AbortPolicy,任务被拒绝时将抛出RejectExecutorException

    //线程池中均为ThreadPoolExecutor.Worker对象
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        //execute()或者submit()时,调用addWorker方法,new Worker(),然后调用Worker.thread.start()
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        //getTask()获取task,即—> while (task != null || (task = getTask()) != null)
        //调用task.run()
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;
    }

5.11 重载

  • jdk通过对方法名+形参列表作为方法签名的唯一性判断,与返回值类型无关

5.12 hashcode

通俗来说:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

5.13 ThreadLocal

  • Thread有一个ThreadLocal.ThreadLocalMap threadLocals成员变量

  • 当ThreadLocal.set(V)时,以**ThreadLocalMap.Entry(ThreadLocal,V)**形式存在于thread.threadLocals对象中

  • Entry以线性探测法来解决hash冲突

    public void set(T value) {
            Thread t = Thread.currentThread();
        	//获取当前线程t.threadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null)
                //设置 K:ThreadLocal,V:value存放在Entry<Referent,Value>数组中
                map.set(this, value);
            else
                createMap(t, value);
        }

5.14 serialVersionUID

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

6. Distribution System

6.1 CAP

  • C(一致性):所有的节点上的数据时刻保持同步
  • A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
  • P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)

6.2 DUBBO

FailCluster

Feature Strength Problem
FailOver 失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用) 重试会带来更长延迟
FailFast 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作 如果有机器正在重启,可能会出现调用失败
FailBack 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作 不可靠,重启丢失
FailSafe 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作 调用信息丢失
Forking 并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作 需要浪费更多服务资源
Broadcast 广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态 速度慢,任意一台报错则报错

方法名相同参数列表不同,在dubbo url中只展示一个方法,invoke过程中通过参数列表类型动态调用重载的方法

7. Rabbitmq

7.1 消息可靠机制

  • 发送端消息的可靠投递:事务、Publisher Confirm模式
  • Exchange -> Queue:消息持久化 + 镜像队列

ReturnCallBack只有在mandatory设置为true时生效,主要用于判断exchange->queue 是否正确路由到,如果没有路由到queue中,returnCallback被调用

ConfirmCallBack用于 producer -> exchange + 在本地缓存已发送的message + 通过confirmCallback或者被确认的ack,将被确认的message从本地删除 + 定时扫描本地的message,如果大于一定时间未被确认,则重发(如果发送后网络断开没有收到ack,重发消息,相比于丢消息,重发消息要好解决的多,consumer端做幂等处理)

  • 消费端消息的消费:ack机制+requeue或者放到死信队列中