java开发面试题

1.1 Java面试资料

1.2 数据格式定义

1 java基础

1.1.1 static final 以及transient的区别

static  静态修饰关键字,能够修饰 变量,程序块,类的方法;javascript

 

当你定义一个static的变量的时候jvm会将将其分配在内存堆上,全部程序对它的引用都会指向这一个地址而不会从新分配内存;css

修饰一个程序块的时候(也就是直接将代码写在static{...})时候,虚拟机就会优先加载静态块中代码,这主要用于系统初始化;html

当修饰一个类方法时候你就能够直接经过类来调用而不须要新建对象。前端

 

final 只能赋值一次;修饰变量、方法及类,当你定义一个final变量时,jvm会将其分配到常量池中,程序不可改变其值;当你定义一个方法时,改方法在子类中将不能被重写;当你修饰一个类时,该类不能被继承。java

 

transientnode

类型修饰符,只能用来修饰字段,若是用transient声明一个实例变量,当对象存储时,它的值不须要维持。换句话来讲就是,用transient关键字标记的成员变量不参与序列化过程。mysql

 

staticfinal使用范围:类、方法、变量。web

 

1.1.2 遍历一个List有哪些不一样的方式?

List初始化话大小为10,扩容增量:原容量的 0.5+1面试

 List<String> strList = new ArrayList<>();redis

  使用for-each循环,

  Iterator<String> it = strList.iterator();

while(it.hasNext()){

  String obj = it.next();

  System.out.println(obj);

}

使用迭代器更加线程安全,由于它能够确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException

 

ArrayList

   线程不安全,查询速度快,底层数据结构是数组结构,

   扩容增量:原容量的 0.5+1  ArrayList的容量为10,一次扩容后是容量为16

   

  Set() 元素无序的、不可重复。

底层实现是一个HashMap(保存数据),实现Set接口默认初始容量为16(为什么是16,见下方对HashMap的描述)加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容扩容增量:原容量的 1 倍如 HashSet的容量为16,一次扩容后是容量为32

           

1.1.3 fail-fastfail-safe有什么区别

Iteratorfail-fast属性与当前的集合共同起做用,所以它不会受到集合中任何改动的影响。

Java.util包中的全部集合类都被设计为fail-fast的,而java.util.concurrent中的集合类都为fail-safe的。

Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException

1.1.4 迭代一个集合的时候,如何避免ConcurrentModificationException

在遍历一个集合的时候,咱们能够使用并发集合类来避免ConcurrentModificationException

好比使用CopyOnWriteArrayList,而不是ArrayList

1.1.5 HashMapHashTable有何不一样?

1HashMap容许keyvaluenull,而HashTable不容许。

2HashTable是同步的,而HashMap不是。因此HashMap适合单线程环境,HashTable适合多线程环境。

3.在Java1.4中引入了LinkedHashMapHashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,可是HashTable不是这样的,它的顺序是不可预知的。

4HashMap提供对keySet进行遍历,所以它是fail-fast的,但HashTable提供对keyEnumeration进行遍历,它不支持fail-fast

5HashTable被认为是个遗留的类,若是你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap

1.1.6 ArrayListLinkedList有何区别?

ArrayListLinkedList二者都实现了List接口,可是它们之间有些不一样。

1.ArrayList是由Array所支持的基于一个索引的数据结构,因此它提供对元素的随机访问,复杂度为O(1),但LinkedList存储一系列的节点数据,每一个节点都与前一个和下一个节点相链接。因此,尽管有使用索引获取元素的方法,内部实现是从起始点开始遍历,遍历到索引的节点而后返回元素,时间复杂度为O(n),比ArrayList要慢。

2.ArrayList相比,在LinkedList中插入、添加和删除一个元素会更快,由于在一个元素被插入到中间的时候,不会涉及改变数组的大小,或更新索引。

3.LinkedListArrayList消耗更多的内存,由于LinkedList中的每一个节点存储了先后节点的引用。

1.1.7 哪些集合类是线程安全的?

VectorHashTablePropertiesStack是同步类,因此它们是线程安全的,能够在多线程环境下使用。

Java1.5并发API包括一些集合类,容许迭代时修改,由于它们都工做在集合的克隆上,因此它们在多线程环境中是安全的。

1.1.8 队列和栈是什么,列出它们的区别?

栈和队列二者都被用来预存储数据。java.util.Queue是一个接口,它的实现类在Java并发包中。

队列容许先进先出(FIFO)检索元素,但并不是老是这样。Deque接口容许从两端检索元素。

栈与队列很类似,但它容许对元素进行后进先出(LIFO)进行检索。

Stack是一个扩展自Vector的类,而Queue是一个接口。

1.1.9 java对象的比较

1.等号(==)

对比对象实例的内存地址(也即对象实例的ID),来判断是不是同一对象实例;又能够说是判断对象实例是否物理相等;

2.equals()

  对比两个对象实例是否相等。

当对象所属的类没有重写根类Objectequals()方法时,equals()判断的是对象实例的ID(内存地址),

是不是同一对象实例;该方法就是使用的等号(==)的判断结果,如Object类的源代码所示:

  public boolean equals(Object obj) {  

return (this == obj);  

}

3.hashCode():

计算出对象实例的哈希码,并返回哈希码,又称为散列函数。

根类ObjecthashCode()方法的计算依赖于对象实例的D(内存地址),故每一个Object对象的hashCode都是惟一的;固然,当对象所对应的类重写了hashCode()方法时,结果就大相径庭了。

1.2.0 Java类为何须要hashCode

总的来讲,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素能够重复;

后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?

这就是 Object.equals方法了。可是,若是每增长一个元素就检查一次,那么当元素不少时,后添加到集合中的元素比较的次数就很是多了。

也就是说,若是集合中如今已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000equals方法。这显然会大大下降效率。

    因而,Java采用了哈希表的原理。哈希算法也称为散列算法,当集合要添加新的元素时,将对象经过哈希算法计算获得哈希值(正整数),而后将哈希值和集合(数组)长度进行&运算,获得该对象在该数组存放的位置索引。若是这个位置上没有元素,它就能够直接存储在这个位置上,不用再进行任何比较了;若是这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就表示发生冲突了,散列表对于冲突有具体的解决办法,但最终还会将新元素保存在适当的位置。这样一来,实际调用equals方法比较的次数就大大下降了,几乎只须要一两次。 简而言之,在集合查找时,hashcode能大大下降对象比较次数,提升查找效率!

1.2.1 JAVA中堆和栈的区别

Java 把内存划分红两种:一种是栈内存,另外一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。

堆内存用来存放由 new 建立的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。

在堆中产生了一个数组或者对象以后,还能够在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,

栈中的这个变量就成了数组或对象的引用变量,之后就能够在程序中使用栈中的引用变量来访问堆中的数组或者对象,

引用变量就至关因而为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其做用域以外后被释放。

而数组和对象自己在堆中分配,即便程序运行到使用 new 产生数组或者对象的语句所在的代码块以外,数组和对象自己占据的内存不会被释放,

数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不肯定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的缘由,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!

1.2.2 java类执行顺序

1. 首先会执行类中static代码块(无论代码块是否在类的开头仍是末尾处),若是这个类有父类,一样会优先查找父类中的static代码块,而后是当前类的static

2..而后会从父类的第一行开始执行,直至代码末尾处,中间无论是有赋值仍是method调用,都会按顺序一一执行(method),普通代码块{ }

3.其次才是父类的构造函数,执行带参数或不带参数的构造函数,依赖于实例化的类的构造函数有没有super父类的带参或不带参的构造函数,上边试验二三已经证实

4..而后会从子类(当前类)的第一行开始执行,直至代码末尾处,中间无论是有赋值仍是method调用,都会按顺序一一执行(method),普通代码块{ }

5.其次会是子类(当前类)的构造函数,按顺序执行。

6.最后是类方法的调用执行,若是子类覆盖了父类的method,执行时会先执行子类覆盖的methodmethod内若是有super.method(),才会调用父类的同名method,不然不会。

1.2.3 Class.forNameClassLoader区别?

class.forName()除了将类的.class文件加载到jvm中以外,还会对类进行解释,执行类中的static块。

classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static.

1.2.4 动态代理与cglib实现的区别?

动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理。

jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。

cglib动态代理是继承并重写目标类,因此目标类和方法不能被声明成final

2. JVM的内存结构,EdenSurvivor比例。

1.2.5 hashmap的数据结构?

HashMap实现原理(数组+链表/红黑树):

数组:存储区间连续,占用内存严重,寻址容易,插入删除困难(须要元素移位)

链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易(只需改动元素中的指针)

用一个数组来存储元素(数组有默认长度),可是这个数组存储的不是基本数据类型。HashMap实现巧妙的地方就在这里,数组存储的元素是一个Entry类,这个类有三个数据域,keyvalue(键值对),next(指向下一个Entry);而这个Entry应该放在数组的哪个位置上(这个位置一般称为位桶或者hash桶,即hash值相同的Entry会放在同一位置,用链表相连),是经过keyhashCode来计算的。

HashTable已算废弃:hashtable的线程安全机制效率是很是差的,如今能找到很是多的替代方案,好比Collections.synchronizedMapcourrenthashmap

jdk1.8ConcurrentHashMap主要作了2方面的改进

改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素做为锁,从而实现了对每一行数据进行加锁,进一步减小并发冲突的几率。

改进二:将原先table数组+单向链表的数据结构,变动为table数组+单向链表+红黑树的结构。对于hash表来讲,最核心的能力在于将key hash以后能均匀的分布在数组中。若是hash以后散列的很均匀,那么table数组中的每一个队列长度主要为0或者1。但实际状况并不是老是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,可是在数据量过大或者运气不佳的状况下,仍是会存在一些队列长度过长的状况,若是仍是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);所以,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度能够下降到O(logN),能够改进性能。

 

答:数组+链表

数组里面放的是链表的第一个值

链表放的是hash后产生冲突的值

1.put(a,b)作了那几个操做?

首先计算ahashing值,而后根据ahashing值找到a所对象的桶(busket),取出当前桶里面所存放的链表,遍历链表,看是否存在keya的对象,若是有,就将原来的值返回,并覆盖上新值b。最后将entry加入到链顶。

2.为何hashmap是无序的?

  由于hashmap的默认容量是16,负载因子是0.75。就是当hashmap填充了75%busket是就会扩容,最小的可能性是(16*0.75),通常为原内存的2倍,而后rehash,这时候致使了hashmap的无序性。

3.concurrentHashMaphashtable的区别?

hashtable是将整个类加了锁,因此整个类的操做性能都比较低,而concurrentHashMap是在busket上面加了锁,实现了锁的细化,能够同时支持16个线程的并发。

4concurrentHashMap是如何保证线程安全的:

 1.就是keyentry都是用final修饰的,从而保证告终构的不可变形

2.value是用volatile修饰的,从而保证了线程的可见性

3.busket操做的时候加了锁(reentrantlock

1.2.6 过滤器和拦截器的区别:

①拦截器是基于java的反射机制的,而过滤器是基于函数回调。

②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。

③拦截器只能对action请求起做用,而过滤器则能够对几乎全部的请求起做用。

④拦截器能够访问action上下文、值栈里的对象,而过滤器不能访问。

⑤在action的生命周期中,拦截器能够屡次被调用,而过滤器只能在容器初始化时被调用一次。

⑥拦截器能够获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,能够调用业务逻辑。

1.2.7 java优化

1、使用数据库链接池和线程池

这两个池都是用于重用对象的,前者能够避免频繁地打开和关闭链接,后者能够避免频繁地建立和销毁线程

2、使用同步代码块替代同步方法

这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能肯定一整个方法都是须要进行同步的,不然尽可能使用同步代码块,避免对那些不须要进行同步的代码也进行了同步,影响了代码执行效率。

3、不要让public方法中有太多的形参

public方法即对外提供的方法,若是给这些方法太多形参的话主要有两点坏处:

②  违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合

② 参数太多势必致使方法调用的出错几率增长

4、不要将数组声明为public static final

由于这毫无心义,这样只是定义了引用为static final,数组的内容仍是能够随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组能够被外部类所改变

5、不要在循环中使用trycatch…,应该把其放在最外层

除非不得已。若是毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为何写出这种垃圾代码来了

6、及时关闭流

Java编程过程当中,进行数据库链接、I/O流操做时务必当心,在使用完毕后,及时关闭以释放资源。由于对这些大对象的操做会形成系统大的开销,稍有不慎,将会致使严重的后果。

7、慎用异常

异常对性能不利。抛出异常首先要建立一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,由于在处理过程当中建立了一个新的对象。异常只能用于错误处理,不该该用来控制程序流程。

8、尽可能重用对象

特别是String对象的使用,出现字符串链接时应该使用StringBuilder/StringBuffer代替。因为Java虚拟机不只要花时间生成对象,之后可能还须要花时间对这些对象进行垃圾回收和处理,所以,生成过多的对象将会给程序的性能带来很大的影响。

9、及时清除再也不须要的会话

为了清除再也不活动的会话,许多应用服务器都有默认的会话超时时间,通常为30分钟。当应用服务器须要保存更多的会话时,若是内存不足,那么操做系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。若是会话要被转储到磁盘,那么必需要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。所以,当会话再也不须要时,应当及时调用HttpSessioninvalidate()方法清除会话。

10、公用的集合类中不使用的数据必定要及时remove

若是一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,由于始终有引用指向它们。因此,若是公用集合里面的某些数据不使用而不去remove掉它们,那么将会形成这个公用集合不断增大,使得系统有内存泄露的隐患。

1.2.8 类加载流程

1.加载:jvmclass字节码文件加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,并在堆内存中生成一个表明这个类的java.lang.class对象,做为方法区类数据的访问入口。

2.连接

2.1 验证:

确保jvm加载的类信息符合jvm规范,没有安全方面的问题;

2.2准备:

正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配;

2.3解析

虚拟机常量池内的符号引用替换为直接引用的过程。(好比String s ="aaa",转化为 s的地址指向“aaa”的地址)

3.初始化:

初始化阶段是执行类构造器方法的过程。类构造器方法是由编译器自动收集类中的全部类变量的赋值动做和静态语句块(static块)中的语句合并产生的。

当初始化一个类的时候,若是发现其父类尚未进行过初始化,则须要先初始化其父类的初始化

虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步

当访问一个java类的静态域时,只有真正声明这个静态变量的类才会被初始化。

 

 

 

 

 

 

多线程

2.1.1 final的做用

1. 避免构造函数重排序、

2. 保证final变量的初始化,必定在构造函数返回以前完成!

private static Sington instance;  new Instance();

底层能够分为3个操做: 分配内存,在内存上初始化成员变量,把instance指向内存。

3个操做,可能重排序,即先把instance指向内存,再初始化成员变量。

2.1.2 

自旋锁: 线程拿不到锁的时候,CPU空转,不放弃CPU,等待别的线程释放锁。前面所讲的CAS乐观锁,便是自旋锁的典型例子。

  很显然,自旋锁只有在多CPU状况下,才可能使用。若是单CPU,占着不放,其余程序就没办法调度了。

阻塞锁: 线程拿不到锁的时候,放弃CPU,进入阻塞状态。等别的线程释放锁以后,再幻醒此线程,调度到CPU上执行。

  自旋锁相比阻塞锁,少了线程切换的时间,所以性能可能更高。但若是自旋不少次,也拿不到锁,则会浪费CPU的资源。

  

独占锁:Synchronized/ReentrantLock都是独占锁,或者叫“排他锁”。1个线程拿到锁以后,其余线程只能等待。 “读”与“读”互斥,“读”与“写”互斥,“写”与“写”互斥

 

共享锁:在笔者看来,共享锁和“读写锁”就是一个概念,读写分离。“读”与“读”不互斥,“读”与“写”互斥,“写”与“写”互斥。由于“读”与“读”不互斥,因此1个线程拿到“读”锁以后,其余线程也能够拿到“读”锁,所以“读”锁是共享的。但1个线程拿到”读“锁以后,另1个线程,拿不到”写“锁;反之亦然!

 

 所谓可重入,就是指某个线程在拿到锁以后,在锁的代码块内部,再次去拿该锁,仍可拿到。

synchronized关键字, Lock都是可重入锁。所以你能够在synchronized方法内部,调用另一个synchronized方法;在lock.lock()的代码块里面,再次调用lock.lock()

2.1.3 Synchronized关键字与Lock的区别

1)有公平/非公平策略

2)有tryLock(),非阻塞方式。Synchronized只有阻塞方式

3)有lockInterruptibly,能够响应中断。Synchronized不能响应中断

4Lock对应的Condition,其使用方式要比Synchronized对应的wait()/notify()更加灵活。

 (5)  2者加锁后,thread所处的状态是不同的:synchronized加锁,thread是处于Blocked状态,Lock枷锁,thread是处于Waiting状态!

 (6)  synchronized关键字内部有自旋机制,先自旋,超过次数,再阻塞;Lock里面没有用自旋机制。

 (7)  synchronized是在JVM层面上实现的,不但能够经过一些监控工具监控synchronized的锁定,并且在代码执行时出现异常,

JVM会自动释放锁定;可是使用Lock则不行,lock是经过代码实现的,要保证锁定必定会被释放,就必须将unLock()放到finally{}中;

2.1.4 ArrayBlockingQueueQueue

一般的Queue,一边是生产者,一边是消费者。一边进,一边出,有一个判空函数,一个判满函数。

而所谓的BlockingQueue,就是指当为空的时候,阻塞消费者线程;当为满的时候,阻塞生产者线程。

2.1.5 线程

1.为何会有线程不安全?

答:由于方法在执行的时候,为了方便回从堆里面将变量copy到栈中,由于栈是线程私有的,因此致使了数据的不一致。

2.线程安全的三要素是什么?

答:可见性,原子性,顺序性。

3.cas 的原理是?

答:cas是根据里面的一个标志位status来判断是否锁被占用了。

2.1.6 volatilesynchronized的区别

Volatile:本质是在告诉jvm当前变量在寄存器(工做内存)中的值是不肯定的,须要从主存中读取; synchronized:则是锁定当前变量,只有当前线程能够访问该变量,其余线程被阻塞住。

volatile仅能使用在变量级别;synchronized则能够使用在变量、方法、和类级别的

volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则能够保证变量的修改可见性和原子性

  volatile不会形成线程的阻塞;synchronized可能会形成线程的阻塞。

  volatile标记的变量不会被编译器优化;synchronized标记的变量能够被编译器优化

  

2.1.7 java线程池

多线程的异步执行方式,虽然可以最大限度发挥多核计算机的计算能力,可是若是不加控制,反而会对系统形成负担。线程自己也要占用内存空间,大量的线程会占用内存资源而且可能会致使Out of Memory。即使没有这样的状况,大量的线程回收也会给GC带来很大的压力。

为了不重复的建立线程,线程池的出现可让线程进行复用。通俗点讲,当有工做来,就会向线程池拿一个线程,当工做完成后,并非直接关闭线程,而是将这个线程归还给线程池供其余任务使用。

 

线程池处理流程:

                 

 

 

 

按照策略处理没法执行新的任务

 

 

建立线程执行任务

将任务存储在队列里

建立线程执行任务

 

 

 

结合上面的流程图来逐行解析,首先前面进行空指针检查,

wonrkerCountOf()方法可以取得当前线程池中的线程的总数,取得当前线程数与核心池大小比较,

若是小于,将经过addWorker()方法调度执行。

若是大于核心池大小,那么就提交到等待队列。

若是进入等待队列失败,则会将任务直接提交给线程池。

若是线程数达到最大线程数,那么就提交失败,执行拒绝策略。

 

addWorker共有四种传参方式。execute使用了其中三种,分别为:

1.addWorker(paramRunnable, true)

线程数小于corePoolSize时,放一个须要处理的taskWorkers Set。若是Workers Set长度超过corePoolSize,就返回false.

2.addWorker(null, false)

放入一个空的taskworkers Set,长度限制是maximumPoolSize。这样一个task为空的worker在线程执行的时候会去任务队列里拿任务,这样就至关于建立了一个新的线程,只是没有立刻分配任务。

3.addWorker(paramRunnable, false)

当队列被放满时,就尝试将这个新来的task直接放入Workers Set,而此时Workers Set的长度限制是maximumPoolSize。若是线程池也满了的话就返回false.

还有一种状况是execute()方法没有使用的addWorker(null, true)

这个方法就是放一个nulltaskWorkers Set,并且是在小于corePoolSize时,若是此时Set中的数量已经达到corePoolSize那就返回false,什么也不干。实际使用中是在prestartAllCoreThreads()方法,这个方法用来为线程池预先启动corePoolSizeworker等待从workQueue中获取任务执行。

执行流程:

1、判断线程池当前是否为能够添加worker线程的状态,能够则继续下一步,不能够return false

    A、线程池状态>shutdown,可能为stoptidyingterminated,不能添加worker线程

    B、线程池状态==shutdownfirstTask不为空,不能添加worker线程,由于shutdown状态的线程池不接收新任务

    C、线程池状态==shutdownfirstTask==nullworkQueue为空,不能添加worker线程,由于firstTask为空是为了添加一个没有任务的线程再从workQueue获取task,而workQueue为      空,说明添加无任务线程已经没有意义

2、线程池当前线程数量是否超过上限(corePoolSize maximumPoolSize),超过了return false,没超过则对workerCount+1,继续下一步

3、在线程池的ReentrantLock保证下,向Workers Set中添加新建立的worker实例,添加完成后解锁,并启动worker线程,若是这一切都成功了,return true,若是添加workerSet失败或启动失败,调用addWorkerFailed()逻辑

2.1.8常见的四种线程池

newFixedThreadPool固定大小的线程池

public static ExecutorService newFixedThreadPool(int var0) {

  return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());

 }

public static ExecutorService newFixedThreadPool(int var0, ThreadFactory var1) {

return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var1);

}

固定大小的线程池,能够指定线程池的大小,该线程池corePoolSizemaximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。

该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会当即执行,若是没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue

迅速增大,存在着耗尽系统资源的问题。并且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工做线程,还会占用必定的系统资源,须要shutdown

 

newSingleThreadExecutor单个线程线程池

public static ExecutorService newSingleThreadExecutor() {

    return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));

    }

public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) {

    return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0));

    }

单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,如有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

 

newCachedThreadPool缓存线程池

public static ExecutorService newCachedThreadPool() {

    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());

 }

public static ExecutorService newCachedThreadPool(ThreadFactory var0) {

   return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);

 }

缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列,    他总会迫使线程池增长新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime60秒),则工做线程将会终止被回收,当提交新任务时,若是没有空闲线程,则建立新线程执行任务,会致使必定的系统开销。若是同时又大量任务被提交,并且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这极可能会很快耗尽系统的资源。

 

newScheduledThreadPool定时线程池

public static ScheduledExecutorService newScheduledThreadPool(int var0) {

        return new ScheduledThreadPoolExecutor(var0);

    }

public static ScheduledExecutorService newScheduledThreadPool(int var0, ThreadFactory var1) {

     return new ScheduledThreadPoolExecutor(var0, var1);

   }

定时线程池,该线程池可用于周期性地去执行任务,一般用于周期性的同步数据。

scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功以后和下一次开始执行的以前的时间。

2.1.9线程池的参数说明

new  ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);

corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会建立一个线程来执行任务,即便其余空闲的基本线程可以执行新任务也会建立线程,等到须要执行的任务数大于线程池基本大小时就再也不建立。若是调用了线程池的prestartAllCoreThreads方法,线程池会提早建立并启动全部基本线程。

runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 能够选择如下几个阻塞队列。

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

PriorityBlockingQueue:一个具备优先级的无限阻塞队列。

maximumPoolSize(线程池最大大小):线程池容许建立的最大线程数。若是队列满了,而且已建立的线程数小于最大线程数,则线程池会再建立新的线程执行任务。值得注意的是若是使用了无界的任务队列这个参数就没什么效果。

ThreadFactory:用于设置建立线程的工厂,能够经过线程工厂给每一个建立出来的线程设置更有意义的名字。

RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采起一种策略处理提交的新任务。这个策略默认状况下是AbortPolicy,表示没法处理新任务时抛出异常。如下是JDK1.5提供的四种策略。

AbortPolicy:直接抛出异常。

CallerRunsPolicy:只用调用者所在线程来运行任务。

DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

DiscardPolicy:不处理,丢弃掉。

固然也能够根据应用场景须要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

keepAliveTime(线程活动保持时间):线程池的工做线程空闲后,保持存活的时间。因此若是任务不少,而且每一个任务执行的时间比较短,能够调大这个时间,提升线程的利用率。

TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)

 

线程池的大小决定着系统的性能,过大或者太小的线程池数量都没法发挥最优的系统性能。

 

固然线程池的大小也不须要作的太过于精确,只须要避免过大和太小的状况。通常来讲,肯定线程池的大小须要考虑CPU的数量,内存大小,任务是计算密集型仍是IO密集型等因素

2.2.0如何选择线程池数量

NCPU = CPU的数量

UCPU = 指望对CPU的使用率 0 UCPU 1

W/C = 等待时间与计算时间的比率

若是但愿处理器达到理想的使用率,那么线程池的最优大小为:

线程池大小=NCPU *UCPU(1+W/C)

Java中使用

int ncpus = Runtime.getRuntime().availableProcessors();

线程池工厂

Executors的线程池若是不指定线程工厂会使用Executors中的DefaultThreadFactory,默认线程池工厂建立的线程都是非守护线程。

使用自定义的线程工厂能够作不少事情,好比能够跟踪线程池在什么时候建立了多少线程,也能够自定义线程名称和优先级。若是将新建的线程都设置成守护线程,当主线程退出后,将会强制销毁线程池。

下面这个例子,记录了线程的建立,并将全部的线程设置成守护线程。

public class ThreadFactoryDemo {

    public static class MyTask1 implements Runnable{

        @Override

        public void run() {

            System.out.println(System.currentTimeMillis()+"Thrad ID:"+Thread.currentThread().getId());

            try {

                Thread.sleep(100);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    public static void main(String[] args){

          MyTask1 task = new MyTask1();

        ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {

            @Override

            public Thread newThread(Runnable r) {

                Thread t = new Thread(r);

                t.setDaemon(true);

                System.out.println("建立线程"+t);

                return  t;

            }

        });

        for (int i = 0;i<=4;i++){

           es.submit(task);

        }

    }

}

扩展线程池

ThreadPoolExecutor是能够拓展的,它提供了几个能够在子类中改写的方法:beforeExecute,afterExecuteterimated

在执行任务的线程中将调用beforeExecuteafterExecute,这些方法中还能够添加日志,计时,监视或统计收集的功能,还能够用来输出有用的调试信息,帮助系统诊断故障。

如下阿里编码规范里面说的一段话:

线程池不容许使用Executors去建立,而是经过ThreadPoolExecutor的方式,这样的处理方式让写的同窗更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:

1newFixedThreadPoolnewSingleThreadExecutor:

  主要问题是堆积的请求处理队列可能会耗费很是大的内存,甚至OOM

2newCachedThreadPoolnewScheduledThreadPool:

  主要问题是线程数最大数是Integer.MAX_VALUE,可能会建立数量很是多的线程,甚至OOM

2.2.1手动建立线程池有几个注意点

1.任务独立。如何任务依赖于其余任务,那么可能产生死锁。例如某个任务等待另外一个任务的返回值或执行结果,那么除非线程池足够大,不然将发生线程饥饿死锁。

2.合理配置阻塞时间过长的任务。若是任务阻塞时间过长,那么即便不出现死锁,线程池的性能也会变得很糟糕。在Java并发包里可阻塞方法都同时定义了限时方式和不限时方式。例如

Thread.join,BlockingQueue.put,CountDownLatch.await等,若是任务超时,则标识任务失败,而后停止任务或者将任务放回队列以便随后执行,这样,不管任务的最终结果是否成功,这种办法都可以保证任务总能继续执行下去。

3.设置合理的线程池大小。只须要避免过大或者太小的状况便可,上文的公式线程池大小=NCPU *UCPU(1+W/C)

4.选择合适的阻塞队列。newFixedThreadPoolnewSingleThreadExecutor都使用了无界的阻塞队列,无界阻塞队列会有消耗很大的内存,若是使用了有界阻塞队列,它会规避内存占用过大的问题,可是当任务填满有界阻塞队列,新的任务该怎么办?在使用有界队列是,须要选择合适的拒绝策略,队列的大小和线程池的大小必须一块儿调节。对于很是大的或者无界的线程池,能够使用SynchronousQueue来避免任务排队,以直接将任务从生产者提交到工做者线程。

下面是Thrift框架处理socket任务所使用的一个线程池,能够看一下FaceBook的工程师是如何自定义线程池的。

  private static ExecutorService createDefaultExecutorService(Args args) {

        SynchronousQueue executorQueue = new SynchronousQueue();

      return new ThreadPoolExecutor(args.minWorkerThreads, args.maxWorkerThreads, 60L, TimeUnit.SECONDS,

                executorQueue);

    }

 

2.1.8 Semaphore 信号量

Semaphore又称信号量,是操做系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。

package concurrent.semaphore;

import java.util.concurrent.Semaphore;

public class Driver {

    // 控制线程的数目为1,也就是单线程

    private Semaphore semaphore = new Semaphore(1);

    public void driveCar() {

        try {

            // 从信号量中获取一个容许机会

            semaphore.acquire();

            System.out.println(Thread.currentThread().getName() + " start at " + System.currentTimeMillis());

            Thread.sleep(1000);

            System.out.println(Thread.currentThread().getName() + " stop at " + System.currentTimeMillis());

            // 释放容许,将占有的信号量归还

            semaphore.release();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

2.1.9 倒计时器:CountDownLatch和循环栅栏:CyclicBarrier

倒计时器是一个很是强大的多线程控制器,好比在放飞火箭的时候,必须先确保各类仪器的检查,引擎才能点火,这种状况就适合使用CountDownLatch

public CountDownLatch(int count)//构造器接受的这个参数就是这个计时器的计数个数

循环栅栏是另一种多线程并发控制工具,和倒计时器的做用相似,可是功能比倒计时器强大并且复杂。

static final CountDownLatch end = new CountDownLatch(10);

end.countDown();

end.await();

 

public CyclicBarrier(int parties, Runnable barrierAction) 

barrierAction就是当计数器一次计数完成后,系统会执行的动做

await()

 

JVM

3.1.1 JVM内存分配

线程安全:虚拟机栈、本地方法栈、程序计数器。

非线程安全:堆,方法区

虚拟机栈:每一个方法被执行时,都会在内存中建立一个空间用来存储方法中的局部变量,方法的出入口等信息。

本地方法栈:每一个本地方法被执行时,都会建立一个内存空间,用来存储本地方法中的局部变量,方法的出入口等信息。

程序计数器:是当前程序所执行的class文件的行号指示器,经过改变行号来决定下一段要执行的字节码指令,跳转,循环,异常处理

堆:每个对象的建立跟分配都是在堆上面进行的,堆分为新生代,老生代。新生代有一个Eden和两个Survivor组成,默认比例是82。也能够使用-XXSurvivorRatio来改变百分比。

方法区:用来存放类的版本,类的方法还有static修饰的对象等信息。

 

对象晋升老生代一共有三个可能:

1.当对象达到成年,经历过15GC(默认15次,可配置),对象就晋升为老生代

2.大的对象会直接在老生代建立

3.新生代跟幸存区内存不足时,对象可能晋升到老生代

你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,包括原理,流程,优缺点。

串行垃圾收集器:收集时间长,停顿时间久

并发垃圾收集器:碎片空间多

CMS:并发标记清除。他的主要步骤有:初始收集,并发标记,从新标记,并发清除(删除),重置

G1:主要步骤:初始标记,并发标记,从新标记,复制清除(整理)

CMS的缺点是对cpu的要求比较高。G1是将内存化成了多块,全部对内段的大小有很大的要求

CMS是清除,因此会存在不少的内存碎片。G1是整理,因此碎片空间较小

 

垃圾回收算法的实现原理。

经常使用的垃圾回收算法有两种: 引用计数和可达性分析

1.引用计数是增长一个字段来标识当前的引用次数,引用计数为0的就是能够GC的。可是引用计数不能解决循环引用的问题。

2. 可达性分析:就是经过一系列GC ROOT的对象做为起点,向下搜索,搜索全部没有与当前对象GC ROOT 有引用关系的对象。这些对象就是能够GC的。

3.1.2 在此解释一下Java的内存机制

Java使用一个主内存来保存变量当前值,而每一个线程则有其独立的工做内存。线程访问变量的时候会将变量的值拷贝到本身的工做内存中,这样,当线程对本身工做内存中的变量进行操做以后,就形成了工做内存中的变量拷贝的值与主内存中的变量值不一样。

Java语言规范中指出:为了得到最佳速度,容许线程保存共享成员变量的私有拷贝,并且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

这样当多个线程同时与某个对象交互时,就必需要注意到要让线程及时的获得共享成员变量的变化。

volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用。

因为使用volatile屏蔽掉了VM中必要的代码优化,因此在效率上比较低,所以必定在必要时才使用此关键字。

3.1.3 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cmsg1

串行收集器  暂停全部应用的线程来工做:单线程

并行收集器  默认的垃圾收集器。暂停全部应用;多线程

G1收集器    用于大堆区域。堆内存分割,并发回收

CMS收集器   多线程扫描,标记须要回收的实例,清除

3.1.4 说一下强引用、软引用、弱引用、虚引用以及他们之间和gc的关系

强引用:new出的对象之类的引用, 
只要强引用还在,永远不会回收 
软引用:引用但非必须的对象,内存溢出异常以前,回收 
弱引用:非必须的对象,对象能生存到下一次垃圾收集发生以前。 
虚引用:对生存时间无影响,在垃圾回收时获得通知。

3.1.5 g1cms区别,吞吐量优先和响应优先的垃圾收集器选择

CMS收集器:一款以获取最短回收停顿时间为目标的收集器,是基于“标记-清除”算法实现的,分为4个步骤:初始标记、并发标记、从新标记、并发清除。

G1收集器:面向服务端应用的垃圾收集器,过程:初始标记;并发标记;最终标记;筛选回收。总体上看是“标记-整理”,局部看是“复制”,不会产生内存碎片。

吞吐量优先的并行收集器:以到达必定的吞吐量为目标,适用于科学技术和后台处理等。

响应时间优先的并发收集器:保证系统的响应时间,减小垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

3.1.6 你知道哪些JVM性能调优

设定堆最小内存大小-Xms。  -Xmx:堆内存最大限制。

设定新生代大小。新生代不宜过小,不然会有大量对象涌入老年代。

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

设定垃圾回收器

年轻代用 -XX:+UseParNewGC (串行) 年老代用-XX:+UseConcMarkSweepGC CMS

设定锁的使用

多线程下关闭偏向锁,比较浪费资源

g1 cms 区别,吞吐量优先和响应优先的垃圾收集器选择

CMS是一种以最短停顿时间为目标的收集器

响应优先选择CMS,吞吐量高选择G1

当出现了内存溢出,你怎么排错

jmap看内存状况,而后用 jstack主要用来查看某个Java进程内的线程堆栈信息

3.1.7 Java 8的内存分代改进

从永久代到元空间,在小范围自动扩展永生代避免溢出

3.1.8 JVM垃圾回收机制,什么时候触发MinorGC等操做

分代垃圾回收机制:不一样的对象生命周期不一样。把不一样生命周期的对象放在不一样代上,不一样代上采用最合适它的垃圾回收方式进行回收。

JVM中共划分为三个代:年轻代、年老代和持久代,

年轻代:存放全部新生成的对象;

年老代:在年轻代中经历了N次垃圾回收仍然存活的对象,将被放到年老代中,故都是一些生命周期较长的对象;

持久代:用于存放静态文件,如Java类、方法等。

新生代的垃圾收集器命名为minor gc”,老生代的GC命名为”Full Gc 或者Major GC.其中用System.gc()强制执行的是Full Gc.

判断对象是否须要回收的方法有两种:

1.引用计数

当某对象的引用数为0时,即可以进行垃圾收集。

2.对象引用遍历

果某对象不能从这些根对象的一个(至少一个)到达,则将它做为垃圾收集。在对象遍历阶段,gc必须记住哪些对象能够到达,以便删除不可到达的对象,这称为标记(marking)对象。

触发GCGarbage Collector)的条件:

1)GC在优先级最低的线程中运行,通常在应用程序空闲即没有应用线程在运行时被调用。

2)Java堆内存不足时,GC会被调用。

3.1.9 EdenSurvivor的比例分配等

默认比例8:1

大部分对象都是朝生夕死。

复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另一块上面。复制算法不会产生内存碎片。

8. 深刻分析了Classloader,双亲委派机制

ClassLoader:类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。Java 源程序(.java 文件)在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

双亲委派机制:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,才本身去加载。

3.2.0 深刻分析了Classloader,双亲委派机制

ClassLoader:类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。Java 源程序(.java 文件)在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

双亲委派机制:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,才本身去加载。

3.2.1 什么是CAS

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操做数:内存地址V,旧的预期值A,要修改的新值B

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改成B

这样说或许有些抽象,咱们来看一个例子:

  1. 在内存地址V当中,存储着值为10的变量。

 

 

       内存地址V

  1. 此时线程1想要把变量的值增长1。对线程1来讲,旧的预期值A=10,要修改的新值B=11

 

 

       内存地址V

     线程1: A=10   B=11

  1. 在线程1要提交更新以前,另外一个线程2抢先一步,把内存地址V中的变量值率先更新成了11

 

 

        内存地址

线程1: A=10   B=11

线程2: 把变量值更新为11

  1. 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

 

 

       内存地址V

线程1: A=10   B=11  A=V的值(10!=11)提交失败

线程2: 把变量值更新为11

 

  1. 线程1从新获取内存地址V的当前值,并从新计算想要修改的新值。此时对线程1来讲,A=11B=12。这个从新尝试的过程被称为自旋。

 

 

      内存地址V

线程1: A=11   B=12  

 

  1. 这一次比较幸运,没有其余线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

 

 

       内存地址V

线程1: A=11   B=12   A==V的值(11==11)

  1. 线程1进行SWAP,把地址V的值替换为B,也就是12

、、

 

       内存地址V

线程1: A=11   B=12   A==V的值(11==11)

地址V的值更新为12

 

从思想上来讲,Synchronized属于悲观锁,悲观地认为程序中的并发状况严重,因此严防死守。CAS属于乐观锁,乐观地认为程序中的并发状况不那么严重,因此让线程不断去尝试更新。

 

 

3.2.1 CAS的缺点:

1.CPU开销较大

在并发量比较高的状况下,若是许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

2.不能保证代码块的原子性

CAS机制所保证的只是一个变量的原子性操做,而不能保证整个代码块的原子性。好比须要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

3.ABA问题

这是CAS机制最大的问题所在。

3.2.2 Java当中CAS的底层实现

利用unsafe提供了原子性操做方法。

首先看一看AtomicInteger当中经常使用的自增方法 incrementAndGet

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

private volatile int value;

public final int get() {
    return value;
}

这段代码是一个无限循环,也就是CAS的自旋。循环体当中作了三件事:

1.获取当前值。

2.当前值+1,计算出目标值。

3.进行CAS操做,若是成功则跳出循环,若是失败则重复上述步骤。

这里须要注意的重点是 get 方法,这个方法的做用是获取变量的当前值。

如何保证得到的当前值是内存中的最新值呢?很简单,用volatile关键字来保证。

接下来看一看compareAndSet方法的实现,以及方法所依赖对象的来历:

public final boolean compareAndSet(int expect, int update) {

        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

private static final Unsafe unsafe = Unsafe.getUnsafe();

private static final long valueOffset;

  static {

        try {

            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));

        } catch (Exception var1) {

            throw new Error(var1);

        }

}

compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset

什么是unsafe呢?Java语言不像CC++那样能够直接访问底层操做系统,可是JVM为咱们提供了一个后门,这个后门就是unsafeunsafe为咱们提供了硬件级别的原子操做。

至于valueOffset对象,是经过unsafe.objectFieldOffset方法获得,所表明的是AtomicInteger对象value成员变量在内存中的偏移量。咱们能够简单地把valueOffset理解为value变量的内存地址。

咱们在上一期说过,CAS机制当中使用了3个基本操做数:内存地址V,旧的预期值A,要修改的新值B

unsafecompareAndSwapInt方法参数包括了这三个基本元素:valueOffset参数表明了Vexpect参数表明了Aupdate参数表明了B

正是unsafecompareAndSwapInt方法保证了CompareSwap操做之间的原子性操做。

3.2.3 什么是ABA问题?怎么解决?

当一个值从A更新成B,又更新会A,普通CAS机制会误判经过检测。

利用版本号比较能够有效解决ABA问题。

什么是ABA呢?假设内存中有一个值为A的变量,存储在地址V当中。

 

 

       内存地址

此时有三个线程想使用CAS的方式更新这个变量值,每一个线程的执行时间有略微的误差。线程1和线程2已经得到当前值,线程3还未得到当前值。

 

 

       内存地址V

线程1: 获取当前值A,指望更新为B

线程2: 获取当前值A,指望更新为B

线程3: 指望更新为A

接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2由于某种缘由被阻塞住,没有作更新操做;线程3在线程1更新以后,得到了当前值B

 

 

       内存地址

线程1: 获取当前值A成功更新为B

线程2: 获取当前值A,指望更新为BBLOCK

线程3: 获取当前值B,指望更新为A

再以后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A

 

 

      内存地址V

线程1: 获取当前值A,成功更新为B,已返回

线程2: 获取当前值A,指望更新为BBLOCK

线程3: 获取当前值B成功更新为A

最后,线程2终于恢复了运行状态,因为阻塞以前已经得到了“当前值”A,而且通过compare检测,内存地址V中的实际值也是A,因此成功把变量值A更新成了B

 

 

       内存地址

线程1: 获取当前值A,成功更新为B,已返回

线程2: 获取“当前值“ A成功更新为B

线程3: 获取当前值B,成功更新为A,已返回

这个过程当中,线程2获取到的变量值A是一个旧值,尽管和当前的实际值相同,但内存地址V中的变量已经经历了A->B->A的改变。

 

当咱们举一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

因为提款机硬件出了点小问题,小灰的提款操做被同时提交两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

理想状况下,应该一个线程更新成功,另外一个线程更新失败,小灰的存款只被扣一次。

线程1首先执行成功,把余额从100改为50。线程2由于某种缘由阻塞了。这时候,小灰的妈妈恰好给小灰汇款50元。

线程2仍然是阻塞状态,线程3执行成功,把余额从50改为100

线程2恢复运行,因为阻塞以前已经得到了“当前值”100,而且通过compare检测,此时存款实际值也是100,因此成功把变量值100更新成了50

本来线程2应当提交失败,小灰的正确余额应该保持为100元,结果因为ABA问题提交成功了。

 

什么意思呢?真正要作到严谨的CAS机制,咱们在Compare阶段不只要比较指望值A和地址V中的实际值,还要比较变量的版本号是否一致。

咱们仍然以最初的例子来讲明一下,假设地址V中存储着变量值A,当前版本号是01。线程1得到了当前值A和版本号01,想要更新为B,可是被阻塞了。

           版本号01

 

 

      内存地址V

线程1: 获取当前值A版本号01,指望更新为B

A。这时候,内存地址V中的变量发生了屡次改变,版本号提高为03,可是变量值仍然是A

           版本号03

 

 

      内存地址V

线程1: 获取当前值A,版本号01,指望更新为B

随后线程1恢复运行,进行Compare操做。通过比较,线程1所得到的值和地址V的实际值都是A,可是版本号不相等,因此这一次更新失败。

           版本号03

 

 

      内存地址V

线程1: 获取当前值A,版本号01,指望更新为B

    A==A  01!=03 更新失败!

Java当中,AtomicStampedReference类就实现了用版本号作比较的CAS机制。

 

 

 

 

 

 

 

 

 

 

 

4 SQL

4.1.1 SQL语句的执行步骤:

1 语法分析 分析语句的语法是否符合规范,衡量语句中各表达式的意义。

2 语义分析 检查语句中涉及的全部数据库对象是否存在,且用户有相应的权限。

3 视图转换 将涉及视图的查询语句转换为相应的对基表查询语句。

4 表达式转换 将复杂的SQL表达式转换为较简单的等效链接表达式。

5 选择优化器 不一样的优化器通常产生不一样的“执行计划”

6 选择链接方式 Oracle有三种链接方式,对多表链接Oracle可选择适当的链接方式。

7 选择链接顺序 对多表链接Oracle选择哪一对表先链接,选择这两表中哪一个表作为源数据表。

8 选择数据的搜索路径 根据以上条件选择合适的数据搜索路径,如是选用全表搜索仍是利用索引或是其余的方式。

9 运行“执行计划”

4.1.2 Mysql各类索引区别:

普通索引:最基本的索引,没有任何限制
惟一索引:与"普通索引"相似,不一样的就是:索引列的值必须惟一,但容许有空值。
主键索引:它 是一种特殊的惟一索引,不容许有空值。 
全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。
组合索引:为了更多的提升mysql效率可创建组合索引,遵循最左前缀原则。建立复合索引时应该将最经常使用                                              (频率)做限制条件的列放在最左边,依次递减。

组合索引最左字段用in是能够用到索引的,最好explain一下select

 

4.1.3 sql调优化

①:建立必要的索引

在常常须要进行检索的字段上建立索引,好比要按照姓名进行检索,那么就应该在姓名字段上建立索引,若是常常要按照员工部门和员工岗位级别进行检索,那么就应该在员工部门和员工岗位级别这两个字段上建立索引。建立索引给检索带来的性能提高每每是巨大的,所以在发现检索速度过慢的时候应该首先想到的就是建立索引。

②:使用预编译查询

程序中一般是根据用户的输入来动态执行SQL,这时应该尽可能使用参数化SQL,这样不只能够避免SQL注入漏洞攻击,最重要数据库会对这些参数化SQL进行预编译,这样第一次执行的时候DBMS会为这个SQL语句进行查询优化而且执行预编译,这样之后再执行这个SQL的时候就直接使用预编译的结果,这样能够大大提升执行的速度。

③:调整Where字句中的链接顺序

    DBMS通常采用自下而上的顺序解析where字句,根据这个原理表链接最好写在其余where条件以前,那些能够过滤掉最大数量记录。

④:尽可能将多条SQL语句压缩到一句SQL

    每次执行SQL的时候都要创建网络链接、进行权限校验、进行SQL语句的查询优化、发送执行结果,这个过程是很是耗时的,所以应该尽可能避免过多的执行SQL语句,可以压缩到一句SQL执行的语句就不要用多条来执行。

⑤:用where字句替换HAVING字句

    避免使用HAVING字句,由于HAVING只会在检索出全部记录以后才对结果集进行过滤,而where则是在聚合前刷选记录,若是能经过where字句限制记录的数目,那就能减小这方面的开销。HAVING中的条件通常用于聚合函数的过滤,除此以外,应该将条件写在where字句中。

⑥:使用表的别名

    当在SQL语句中链接多个表时,请使用表的别名并把别名前缀于每一个列名上。这样就能够减小解析的时间并减小哪些友列名歧义引发的语法错误。

⑦:在inexists中一般状况下使用EXISTS,由于in不走索引。

⑧:避免在索引上使用计算

    where字句中,若是索引列是计算或者函数的一部分,DBMS的优化器将不会使用索引而使用全表查询,函数属于计算的一种效率低:select * from person where salary*12>25000(salary是索引列)效率高:select * from person where salary>25000/12(salary是索引列)

⑨:用union all替换union

    SQL语句须要union两个查询结果集合时,即便检索结果中不会有重复的记录,若是使用union这两个结果集一样会尝试进行合并,而后在输出最终结果前进行排序,所以若是能够判断检索结果中不会有重复的记录时候,应该用union all,这样效率就会所以获得提升。

⑩:避免SQL中出现隐式类型转换

    当某一张表中的索引字段在做为where条件的时候,若是进行了隐式类型转换,则此索引字段将会不被识别,由于隐式类型转换也属于计算,因此此时DBMS会使用全表扫面。最后须要注意的是:防止检索范围过宽若是DBMS优化器认为检索范围过宽,那么将放弃索引查找而使用全表扫描。下面几种可能形成检索范围过宽的状况。

    a、使用is not null或者不等于判断,可能形成优化器假设匹配的记录数太多。

    b、使用like运算符的时候,“a%”将会使用索引,而“a%c”和“%a”则会使用全表扫描,所以“a%c”和“%a”不能被有效的评估匹配的数量。

4.1.3 数据库的的锁:行锁,表锁;乐观锁,悲观锁

1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。

2) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。

3) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,能够使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库若是提供相似于write_condition机制的其实都是提供的乐观锁。

4.1.4 B+树和LSM树区别

B+树:

1)根节点和枝节点很简单,分别记录每一个叶子节点的最小值,并用一个指针指向叶子节点。

叶子节点里每一个键值都指向真正的数据块(如Oracle里的RowID),每一个叶子节点都有前指针和后指针,这是为了作范围查询时,叶子节点间能够直接跳转,从而避免再去回溯至枝和跟节点,B+树做为索引,减小磁盘IO数量。

2B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上每每不连续,甚至分离的很远,但作范围查询时,会产生大量读随机IO对于大量的随机写也同样。举一个插入key跨度很大的例子,如7->1000->3->2000 ... 新插入的数据存储在磁盘上相隔很远,会产生大量的随机写IO.从上面能够看出,低下的磁盘寻道速度严重影响性能(近些年来,磁盘寻道速度的发展几乎处于停滞的状态)。

 

LSM树:存储引擎和B树存储引擎同样,LSM树分为两个部分,一部分在磁盘一部分在内存,当内存空间逐渐被占满以后,LSM会把这些有序的键刷新到磁盘,同时和磁盘中的LSM树合并成一个文件。读取是更新最新操做到磁盘,读取慢(先取内存,而后读磁盘),牺牲读性能,提升写性能,磁盘顺序写,周期调整磁盘文件。

 

总结:

1B+树的特色决定了可以对主键进行高效的查找和删除,B+树可以提供高效的的范围扫描功能得益于相互链接且按主键有序,扫描时避免了耗时的遍历操做。

LSM树在查找时先查找内存的存储,若是在内存中未命中就去磁盘文件中查找文件,找到key以后返回最新的版本。

B树和LSM树最主要的区别在于他们的结构和如何利用硬件,特别是磁盘。

2)在没有太多的修改时,B+树表现得很好,由于修改要求执行高代价的优化操做以保证查询能在有限的时间内完成。LSM以磁盘传输速率工做,并能较好地扩展以处理大量数据,他们使用日志文件和内存存储来将随机写转换成顺序写,所以也可以保证稳定的数据插入速率。因为读写分离,两个操做也不存在冲突的问题。

3LSM树的主要目标是快速的创建索引,B树是创建索引的通用技术,可是在大并发插入数据的状况下,B树须要大量的随机IO,这些随机IO严重影响索引创建速度。LSM经过磁盘序列写,来达到最优的写性能,由于这个下降了磁盘的寻道次数,一次IO能够写入多个索引块。

4.1.5 SQLNOSQL区别

 

 

 

5 Spring

5.1.1 BeanFactoryApplicationContext有什么区别?

BeanFactory 能够理解为含有bean集合的工厂类。BeanFactory 包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。

BeanFactory还能在实例化对象的时生成协做类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。

从表面上看,application context如同bean factory同样具备bean定义、bean关联关系的设置,根据请求分发bean的功能。但application context在此基础上还提供了其余的功能。

提供了支持国际化的文本消息

统一的资源文件读取方式

已在监听器中注册的bean的事件

如下是三种较常见的 ApplicationContext 实现方式:

1.ClassPathXmlApplicationContext:从classpathXML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。

ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);

2.FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。

ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);

3. XmlWebApplicationContext:由Web应用的XML文件读取上下文。

5.1.2 Spring有几种配置方式?

Spring配置到应用开发中有如下三种方式:

基于XML的配置

基于注解的配置

基于Java的配置

5.1.3 Spring Bean的做用域之间有什么区别?

singleton:这种bean范围是默认的,这种范围确保无论接受到多少个请求,每一个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。

prototype:原形范围与单例范围相反,为每个bean请求提供一个实例。

request:在请求bean范围内会每个来自客户端的网络请求建立一个实例,在请求完成之后,bean会失效并被垃圾回收器回收。

Session:与请求范围相似,确保每一个session中有一个bean的实例,在session过时后,bean会随之失效。

global-sessionglobal-sessionPortlet应用相关。当你的应用部署在Portlet容器中工做时,它包含不少portlet。若是你想要声明让全部的portlet共用全局的存储变量的话,那么这全局变量须要存储在global-session中。全局做用域与Servlet中的session做用域效果相同。

5.1.4 spring工做原理

1.spring mvc的全部请求都提交给DispatcherServlet,它会委托应用系统的其余模块负责对请求进行真正的处理工做。

2.DispatcherServlet查询一个或多个HandlerMapping,找处处理请求的Controller.

3.DispatcherServlet请求提交到目标Controller

4.Controller进行业务逻辑处理后,会返回一个ModelAndView

5.Dispathcher查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象

6.视图对象负责渲染返回给客户端。

5.1.5 spring事物

1、事务的基本原理

Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是没法提供事务功能的。对于纯JDBC操做数据库,想要用到事务,能够按照如下步骤进行:

    获取链接 Connection con = DriverManager.getConnection()

    开启事务con.setAutoCommit(true/false);

    执行CRUD

    提交事务/回滚事务 con.commit() / con.rollback();

关闭链接 conn.close();

2、Spring 事务的传播属性

所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义 ,

PROPAGATION_REQUIRED     支持当前事务,         没有事务,就新建一个事务。这是最多见的

选择,也是 Spring 默认的事务的传播。

PROPAGATION_REQUIRES_NEW  新建事务,若是当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚以后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也能够不处理回滚操做

PROPAGATION_SUPPORTS     支持当前事务,若是当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY     支持当前事务,若是当前没有事务,就抛出异常。

PROPAGATION_NOT_SUPPORTED  以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER     以非事务方式执行,若是当前存在事务,则抛出异常。

PROPAGATION_NESTED     

若是一个活动的事务存在,则运行在一个嵌套的事务中。若是没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个能够回滚的保存点。内部事务的回滚不会对外部事务形成影响。它只对DataSourceTransactionManager事务管理器起效。

 

3、数据库隔离级别

隔离级别     隔离级别的值     致使的问题

Read-Uncommitted     0     致使脏读

Read-Committed     1     避免脏读,容许不可重复读和幻读

Repeatable-Read     2     避免脏读,不可重复读,容许幻读

Serializable     3     串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重

脏读:    一事务对数据进行了增删改,但未提交,另外一事务能够读取到未提交的数据。若是第一个事务这时候回滚了,那么第二个事务就读到了脏数据。

不可重复读:一个事务中发生了两次读操做,第一次读操做和第二次操做之间,另一个事务对数据进行了修改,这时候两次读取的数据是不一致的。

幻读:第一个事务对必定范围的数据进行批量修改,第二个事务在这个范围增长一条数据,这时候第一个事务就会丢失对新增数据的修改。

总结:

隔离级别越高,越能保证数据的完整性和一致性,可是对并发性能的影响也越大。

大多数的数据库默认隔离级别为 Read Commited,好比 SqlServerOracle

少数数据库默认隔离级别为:Repeatable Read 好比: MySQL InnoDB

 

4、Spring中的隔离级别

常量     解释

ISOLATION_DEFAULT     这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。

ISOLATION_READ_UNCOMMITTED     这是事务最低的隔离级别,它充许另一个事务能够看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

ISOLATION_READ_COMMITTED     保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。

ISOLATION_REPEATABLE_READ 这种事务隔离级别能够防止脏读,不可重复读。可是可能出现幻像读。

ISOLATION_SERIALIZABLE   这是花费最高代价可是最可靠的事务隔离级别。事务被处理为顺序执行。

5.1.6 spring事物回滚机制 (捕获异常不抛出就不会回滚)

当异常被捕获catch的时候,spring的事物则不会回滚

为何不会滚呢??

spring aop  异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认状况下aop只捕获runtimeexception的异常;

解决方案:

  1. 例如service层处理事务,那么service中的方法中不作异常捕获,或者在catch语句中最后增长throw new

RuntimeException()语句,以便让aop捕获异常再去回滚,而且在service上层(webservice客户端,viewaction)要继续捕获这个异常并处理

    catch (Exception e) {

            json.setStatus(StatusCode.ERROR);

            json.setMessage(e.getMessage());

            System.out.println("添加用户异常,报错信息:"+e.getMessage());

            //继续抛出异常

            throw new RuntimeException();

 }

  1. service层方法的catch语句中增长:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(如今项目的作法)

  1. 在这个代码所在的方法上加上rollbackFor ,

形如:@Transactional(readOnly = true, rollbackFor = Exception.class)

5.1.7 Spring各类依赖注入注解的区别

注解注入顾名思义就是经过注解来实现注入,Spring和注入相关的常见注解有AutowiredResourceQualifierServiceControllerRepositoryComponent

Autowired是自动注入,自动从spring的上下文找到合适的bean来注入。

Resource用来指定名称注入。

QualifierAutowired配合使用,指定bean的名称。

Service,用于标注业务层组件。

Controller用于标注控制层组件(如struts中的action)。

Repository 用于标注数据访问组件,即DAO组件。

Component泛指组件,,当组件很差归类的时候,咱们能够使用这个注解进行标注。

spring扫描ServiceControllerRepository Component注解配置时,会标记这些类要生成bean

5.1.8 AutowiredResource的区别:

1@Autowired@Resource均可以用来装配bean.均可以写在字段上,或写在setter方法上。

2@Autowired默认按类型装配(这个注解是属业spring的),默认状况下必需要求依赖对象必须存在,若是要容许null值,能够设置它的required属性为false,如:@Autowired(required=false),若是咱们想使用名称装配能够结合@Qualifier注解进行使用,以下:

@Autowired() @Qualifier("baseDao")

private BaseDao baseDao;

3@Resource(这个注解属于J2EE的),默认安装名称进行装配,名称能够经过name属性进行指定,若是没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,若是注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。可是须要注意的是,若是name属性一旦指定,就只会按照名称进行装配。

5.1.9 ApplicationContextAware 接口的做用

当一个类实现了这个接口以后,这个类就能够方便地得到 ApplicationContext 中的全部bean。换句话说,就是这个类能够直接获取Spring配置文件中,全部有引用到的bean对象。

1、定义一个工具类,实现 ApplicationContextAware,实现 setApplicationContext方法

public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override

    public void setApplicationContext(ApplicationContext context)

            throws BeansException {

        SpringContextUtils.context = context;

    }

    public static ApplicationContext getContext(){

        return context;

    }

}

2、在Spring配置文件中注册该工具类

<!--Springbean获取的工具类-->

<bean id="springContextUtils" class="com.zker.common.util.SpringContextUtils" />

3、编写方法进行使用

UserDao userDao = (UserDao)SpringContextUtils.getContext().getBean("userDao");

5.2.1 Spring 事件传播机制

1. 创建事件类,继承 ApplicationEvent 父类

2. 创建监听类,实现 ApplicationListener 接口

3. 在配置文件 bean.xml 中注册写好的全部 事件类 和 监听类

4. 须要发布事件的类 要实现 ApplicationContextAware 接口,并获取 ApplicationContext 参数

ActionEvent event = new ActionEvent(username);  

SpringContextUtils.applicationContext.publishEvent(event);  

5.2.0 Spring线程池ThreadPoolTaskExecutor配置及详情

1. ThreadPoolTaskExecutor配置

<!-- spring thread pool executor -->           

    <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

        <!-- 线程池维护线程的最少数量 -->

        <property name="corePoolSize" value="5" />

        <!-- 容许的空闲时间 -->

        <property name="keepAliveSeconds" value="200" />

        <!-- 线程池维护线程的最大数量 -->

        <property name="maxPoolSize" value="10" />

        <!-- 缓存队列 -->

        <property name="queueCapacity" value="20" />

        <!-- 对拒绝task的处理策略 -->

        <property name="rejectedExecutionHandler">

            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />

        </property>

</bean>

属性字段说明

corePoolSize:线程池维护线程的最少数量

keepAliveSeconds:容许的空闲时间

maxPoolSize:线程池维护线程的最大数量

queueCapacity:缓存队列

rejectedExecutionHandler:对拒绝task的处理策略

2. execute(Runable)方法执行过程

若是此时线程池中的数量小于corePoolSize,即便线程池中的线程都处于空闲状态,也要建立新的线程来处理被添加的任务。

若是此时线程池中的数量等于 corePoolSize,可是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

若是此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,而且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

若是此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,而且线程池中的数量等于maxPoolSize,那么经过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,若是三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池能够动态的调整池中的线程数。

 

 

 

 

6 redis

6.1.1 使用redis有哪些好处?

1.速度快,由于数据存在内存中,相似于HashMapHashMap的优点就是查找和操做的时间复杂度都是O(1)

2.支持丰富数据类型,支持stringlistsetsorted sethash

3.支持事务,操做都是原子性,所谓的原子性就是对数据的更改要么所有执行,要么所有不执行

4.丰富的特性:可用于缓存,消息,按key设置过时时间,过时后将会自动删除

6.1.2 redis相比memcached有哪些优点?

1.memcached全部的值均是简单的字符串,redis做为其替代者,支持更为丰富的数据类型

2.redis的速度比memcached快不少

3.redis能够持久化其数据

4.Redis支持数据的备份,即master-slave模式的数据备份。

5.使用底层模型不一样, 它们之间底层实现方式 以及与客户端之间通讯的应用协议不同。

Redis直接本身构建了VM 机制 ,由于通常的系统调用系统函数的话,会浪费必定的时间去移动和请求。

6.value大小:redis最大能够达到1GB,而memcache只有1MB

6.1.2 redis常见性能问题和解决方案:

(1) Master最好不要作任何持久化工做,如RDB内存快照和AOF日志文件

(2) 若是数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和链接的稳定性,MasterSlave最好在同一个局域网内

(4) 尽可能避免在压力很大的主库上增长从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...

这样的结构方便解决单点故障问题,实现SlaveMaster的替换。若是Master挂了,能够马上启用Slave1Master,其余不变。

6.1.3 Redis的回收策略

volatile-lru:从已设置过时时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过时时间的数据集(server.db[i].expires)中挑选将要过时的数据淘汰

volatile-random:从已设置过时时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

注意这里的6种机制,volatileallkeys规定了是对已设置过时时间的数据集淘汰数据仍是从所有数据集淘汰数据,后面的lruttl以及random是三种不一样的淘汰策略,再加上一种no-enviction永不回收的策略。

  使用策略规则:

  1、若是数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru

  2、若是数据呈现平等分布,也就是全部的数据访问频率都相同,则使用allkeys-random

6.1.4 redis持久化2种模式?

一:快照模式

  或许在用Redis之初的时候,就据说过redis有两种持久化模式,第一种是SNAPSHOTTING模式,仍是一种是AOF模式,并且在实战场景下用的最多的莫过于SNAPSHOTTING模式,这个不须要反驳吧,并且你可能还知道,使用SNAPSHOTTING模式,须要在redis.conf中设置配置参数,好比下面这样:

save 900 1

save 300 10

save 60 10000

上面三组命令也是很是好理解的,就是说900指的是“秒数”,1指的是“change次数”,接下来若是在“900s“内有1次更改,那么就执行save保存,一样的道理,若是300s内有10change60s内有1wchange,那么也会执行save操做,就这么简单,看了我刚才说了这么几句话,是否是有种直觉在告诉你,有两个问题是否是要澄清一下:

  1. 上面这个操做应该是redis自身进行的同步操做,请问是否能够手工执行save呢?

固然能够进行手工操做,redis提供了两个操做命令:savebgsave,这两个命令都会强制将数据刷新到硬盘中,以下:

先设值set name jack   后执行save 命令

先设值set age 20  后执行bgsave 命令

Redis 持久化:

 提供了多种不一样级别的持久化方式:一种是RDB,另外一种是AOF.

RDB 持久化能够在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。

AOF 持久化记录服务器执行的全部写操做命令,并在服务器启动时,经过从新执行这些命令来还原数据集。 AOF 文件中的命令所有以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还能够在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。Redis 还能够同时使用 AOF 持久化和 RDB 持久化。 在这种状况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 由于 AOF 文件保存的数据集一般比 RDB 文件所保存的数据集更完整。你甚至能够关闭持久化功能,让数据只在服务器运行时存在。

 

6.1. 5  RDB的优缺点

RDB 的优势:

RDB 是一个很是紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件很是适合用于进行备份: 好比说,你能够在最近的 24 小时内,每小时备份一次 RDB 文件,而且在每月的每一天,也备份一个 RDB 文件。 这样的话,即便赶上问题,也能够随时将数据集还原到不一样的版本。RDB 很是适用于灾难恢复(disaster recovery):它只有一个文件,而且内容都很是紧凑,能够(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。RDB 能够最大化 Redis 的性能:父进程在保存 RDB 文件时惟一要作的就是 fork 出一个子进程,而后这个子进程就会处理接下来的全部保存工做,父进程无须执行任何磁盘 I/O 操做。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

RDB 的缺点:

若是你须要尽可能避免在服务器故障时丢失数据,那么 RDB 不适合你。 虽然 Redis 容许你设置不一样的保存点(save point)来控制保存 RDB 文件的频率, 可是, 由于RDB 文件须要保存整个数据集的状态, 因此它并非一个轻松的操做。 所以你可能会至少 5 分钟才保存一次 RDB 文件。 在这种状况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工做。 在数据集比较庞大时, fork() 可能会很是耗时,形成服务器在某某毫秒内中止处理客户端; 若是数据集很是巨大,而且 CPU 时间很是紧张的话,那么这种中止时间甚至可能会长达整整一秒。 虽然 AOF 重写也须要进行 fork() ,但不管 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。

 

6.1. 6  AOF的优缺点

AOF 的优势:

使用 AOF 持久化会让 Redis 变得很是耐久(much more durable):你能够设置不一样的 fsync 策略,好比无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然能够保持良好的性能,而且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,因此主线程能够继续努力地处理命令请求)。AOF 文件是一个只进行追加操做的日志文件(append only log), 所以对 AOF 文件的写入不须要进行 seek , 即便日志由于某些缘由而包含了未写入完整的命令(好比写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也能够轻易地修复这种问题。

Redis 能够在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操做是绝对安全的,由于 Redis 在建立新 AOF 文件的过程当中,会继续将命令追加到现有的 AOF 文件里面,即便重写过程当中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件建立完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操做。AOF 文件有序地保存了对数据库执行的全部写入操做, 这些写入操做以 Redis 协议的格式保存, 所以 AOF 文件的内容很是容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(exportAOF 文件也很是简单: 举个例子, 若是你不当心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要中止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就能够将数据集恢复到 FLUSHALL 执行以前的状态。

AOF 的缺点:

对于相同的数据集来讲,AOF 文件的体积一般要大于 RDB 文件的体积。根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在通常状况下, 每秒 fsync 的性能依然很是高, 而关闭 fsync 可让 AOF 的速度和 RDB 同样快, 即便在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 能够提供更有保证的最大延迟时间(latency)。AOF 在过去曾经发生过这样的 bug : 由于个别命令的缘由,致使 AOF 文件在从新载入时,没法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH 就曾经引发过这样的 bug 。) 测试套件里为这种状况添加了测试: 它们会自动生成随机的、复杂的数据集, 并经过从新载入这些数据来确保一切正常。 虽然这种 bug AOF 文件中并不常见, 可是对比来讲, RDB 几乎是不可能出现这种 bug 的。

 

6.1. 7  RDB AOF 应该用哪个?

通常来讲,若是想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。若是你很是关心你的数据,但仍然能够承受数分钟之内的数据丢失, 那么你能够只使用 RDB 持久化。有不少用户都只使用 AOF 持久化, 但咱们并不推荐这种方式: 由于定时生成 RDB 快照(snapshot)很是便于进行数据库备份, 而且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此以外, 使用 RDB 还能够避免以前提到的 AOF 程序的 bug 。由于以上提到的种种缘由, 将来咱们可能会将 AOF RDB 整合成单个持久化模型。 (这是一个长期计划。)

 

RDB 快照:

在默认状况下, Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你能够对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被知足时, 自动保存一次数据集。你也能够经过调用 SAVE 或者 BGSAVE , 手动让 Redis 进行数据集保存操做。好比说, 如下设置会让 Redis 在知足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集:

save 60 1000

这种持久化方式被称为快照(snapshot)。

 

6.1. 8  快照的运做方式

Redis 须要保存 dump.rdb 文件时, 服务器执行如下操做:

Redis 调用 fork() ,同时拥有父进程和子进程。

子进程将数据集写入到一个临时 RDB 文件中。

当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工做方式使得 Redis 能够从写时复制(copy-on-write)机制中获益。

只进行追加操做的文件(append-only fileAOF

快照功能并非很是耐久(durable): 若是 Redis 由于某些缘由而形成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。尽管对于某些程序来讲, 数据的耐久性并非最重要的考虑因素, 可是对于那些追求彻底耐久能力(full durability)的程序来讲, 快照功能就不太适用了。

1.1 版本开始, Redis 增长了一种彻底耐久的持久化方式: AOF 持久化。

你能够经过修改配置文件来打开 AOF 功能:

appendonly yes

从如今开始, 每当 Redis 执行一个改变数据集的命令时(好比 SET), 这个命令就会被追加到 AOF 文件的末尾。

这样的话, Redis 从新启时, 程序就能够经过从新执行 AOF 文件中的命令来达到重建数据集的目的。

6.1. 9  AOF重写

由于 AOF 的运做方式是不断地将命令追加到文件的末尾, 因此随着写入命令的不断增长, AOF 文件的体积也会变得愈来愈大。举个例子, 若是你对一个计数器调用了 100 INCR , 那么仅仅是为了保存这个计数器的当前值, AOF 文件就须要使用 100 条记录(entry)。然而在实际上, 只使用一条 SET 命令已经足以保存计数器的当前值了, 其他 99 条记录实际上都是多余的。为了处理这种状况, Redis 支持一种有趣的特性: 能够在不打断服务客户端的状况下, 对 AOF 文件进行重建(rebuild)。执行 BGREWRITEAOF 命令, Redis 将生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令。

 

6.2. 0  AOF有多耐久

你能够配置 Redis 多久才将数据 fsync 到磁盘一次。

有三个选项:

每次有新命令追加到 AOF 文件时就执行一次 fsync :很是慢,也很是安全。

每秒 fsync 一次:足够快(和使用 RDB 持久化差很少),而且在故障时只会丢失 1 秒钟的数据。

从不 fsync :将数据交给操做系统来处理。更快,也更不安全的选择。

推荐(而且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略能够兼顾速度和安全性。

老是 fsync 的策略在实际使用中很是慢, 即便在 Redis 2.0 对相关的程序进行了改进以后还是如此 —— 频繁调用 fsync 注定了这种策略不可能快得起来。

6.2. 1  若是 AOF 文件出错了,怎么办?

服务器可能在程序正在对 AOF 文件进行写入时停机, 若是停机形成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。

当发生这种状况时, 能够用如下方法来修复出错的 AOF 文件:

为现有的 AOF 文件建立一个备份。

使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。

$ redis-check-aof --fix

(可选)使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不一样之处。

重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

AOF 的运做方式

AOF 重写和 RDB 建立快照同样,都巧妙地利用了写时复制机制。

6.2. 2  AOF 重写的执行步骤

Redis 执行 fork() ,如今同时拥有父进程和子进程。

子进程开始将新 AOF 文件的内容写入到临时文件。对于全部新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即便在重写的中途发生停机,现有的 AOF 文件也仍是安全的。当子进程完成重写工做时,它给父进程发送一个信号,父进程在接收到信号以后,将内存缓存中的全部数据追加到新 AOF 文件的末尾。如今 Redis 原子地用新文件替换旧文件,以后全部命令都会直接追加到新 AOF 文件的末尾。

为最新的 dump.rdb 文件建立一个备份。

将备份放到一个安全的地方。

执行如下两条命令:

redis-cli> CONFIG SET appendonly yes

redis-cli> CONFIG SET save ""

确保命令执行以后,数据库的键的数量没有改变。

确保写命令会被正确地追加到 AOF 文件的末尾。

步骤 3 执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件建立完成为止, 以后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。

步骤 3 执行的第二条命令用于关闭 RDB 功能。 这一步是可选的, 若是你愿意的话, 也能够同时使用 RDB AOF 这两种持久化功能。

别忘了在 redis.conf 中打开 AOF 功能! 不然的话, 服务器重启以后, 以前经过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。

6.2. 3  RDB AOF 之间的相互做用:

在版本号大于等于 2.4 Redis 中, BGSAVE 执行的过程当中, 不能够执行 BGREWRITEAOF 。 反过来讲, 在 BGREWRITEAOF 执行的过程当中, 也不能够执行 BGSAVE

这能够防止两个 Redis 后台进程同时对磁盘进行大量的 I/O 操做。

若是 BGSAVE 正在执行, 而且用户显示地调用 BGREWRITEAOF 命令, 那么服务器将向用户回复一个 OK 状态, 并告知用户, BGREWRITEAOF 已经被预约执行: 一旦 BGSAVE 执行完毕, BGREWRITEAOF 就会正式开始。当 Redis 启动时, 若是 RDB 持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 由于 AOF 文件所保存的数据一般是最完整的。

 

6.2. 4  备份 Redis 数据:

Redis 对于数据备份是很是友好的, 由于你能够在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被建立, 就不会进行任何修改。 当服务器要建立一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用  原子地用临时文件替换原来的 RDB 文件。这也就是说, 不管什么时候, 复制 RDB 文件都是绝对安全的。

6.2.6 Redis常见命令

flusall 清除reids全部裤

flushdb 清楚当前裤

dbsize 查看当前数据库的key数量大小   dbsize

select 切换数据库             select  1

move  把当前key 移动到哪一个库  move key  1

expire 设置当前key过时,单位秒  expire k1 10

ttl   查看当前key的过时时间,-1表示永不过时,-2表示已过时   ttl k1

type 查看当前key的数据类型  type k1

exists  判断某个key是否存在  exists  key1

incr  对数字类型相加   incr k1

decr

incrby

decrby

save 立刻进行快照

RDB是整个内存的压缩过的SnapshotRDB的数据结构,key配置符合的快照触发条件,

默认:

1分钟内改了1万次

5分钟内改了了10

15分钟内改了1

 

 

 

 

 

7 dubbo

7.1.1 Dubbo服务集群容错配置-集群容错模式

1.failover cluster

失败自动切换,当出现失败,重试其余服务器(缺省),一般用于读操做,但重试会带来更长的延时,可经过retries=2”来设置重试次数(不含第一次)

<dubbo:service retries="2">

或者

<dubbo:reference retries="2">

或者

<dubbo:reference>

   <dubbo:method name="findFoo" retries=2>

<dubbo:reference/>

2.failfast cluster

  快速失效,只发起一次调用,失败当即报错。一般用于非幂等性写操做,好比说新增记录

<dubbo:service cluster="failfast">

或者

<dubbo:reference cluster="failfast"

3.failsaft cluster

   失败安全,出现异常时,直接忽略,一般用于写入审计日志等操做

<dubbo:service cluster="failsafe">

或者

<dubbo:reference cluster="failsafe">

4.failback cluster

  失败自动恢复,后台记录失败请求,定时重发,一般用于消息通知操做

<dubbo:service cluster="failback">

或者

<dubbo:reference cluster="failback">

5.forking cluster

并行调用多个服务器,只要一个成功即返回。一般用于实时性要求较高的读操做,但须要浪费更多的服务器资源。可经过forks=2”来设置最大并行数。

<dubbo:service cluster="forking">

或者

<dubbo:reference cluster="forking">

7.1.2 Dubbo负载均衡策略

Random LoadBalance

随机,按权重设置随机几率。

在一个截面上碰撞的几率高,但调用量越大分布越均匀,并且按几率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率。

存在慢的提供者累积请求问题,好比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,长此以往,全部请求都卡在调到第二台上。

解决办法 :结合权重,把第二台机(性能低的)的权重设置低一点

LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用先后计数差。

使慢的提供者收到更少请求,由于越慢的提供者的调用先后计数差会越大。

ConsistentHash LoadBalance

一致性Hash,相同参数的请求老是发到同一提供者。

当某一台提供者挂时,本来发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引发剧烈变更。

算法参见:http://en.wikipedia.org/wiki/Consistent_hashing

缺省只对第一个参数Hash,若是要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />

缺省用160份虚拟节点,若是要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />

7.1.3 Dubbo线程模型

事件处理线程说明:

若是事件处理的逻辑能迅速完成,而且不会发起新的IO请求,好比只是在内存中记个标识,则直接在IO线程上处理更快,由于减小了线程池调度。

但若是事件处理逻辑较慢,或者须要发起新的IO请求,好比须要查询数据库,则必须派发到线程池,不然IO线程阻塞,将致使不能接收其它请求。

若是用IO线程处理事件,又在事件处理过程当中发起新的IO请求,好比在链接事件中发起登陆请求,会报“可能引起死锁”

Dispatcher

all 全部消息都派发到线程池,包括请求,响应,链接事件,断开事件,心跳等。

direct 全部消息都不派发到线程池,所有在IO线程上直接执行。

message 只有请求响应消息派发到线程池,其它链接断开事件,心跳等消息,直接在IO线程上执行。

execution 只请求消息派发到线程池,不含响应,响应和其它链接断开事件,心跳等消息,直接在IO线程上执行。

connection IO线程上,将链接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

ThreadPool

fixed 固定大小线程池,启动时创建线程,不关闭,一直持有。(缺省)

cached 缓存线程池,空闲一分钟自动删除,须要时重建。

limited 可伸缩线程池,但池中的线程数只会增加不会收缩。(为避免收缩时忽然来了大流量引发的性能问题)

<dubbo:protocolname="dubbo"dispatcher="all"threadpool="fixed"threads="100"/>(默认配置)

(尽可能不要使用 root 用户来部署应用程序,避免资源耗尽后没法登陆操做系统。

 由于root用户默认没有限制线程数,若是线程过多,会使资源占用不少,致使不能关机,只能硬关机)

7.1.4 dubbo架构

Provider: 暴露服务的提供方。

Consumer:调用远程服务的服务消费方。

Registry: 服务注册中心和发现中心。

Monitor: 统计服务和调用次数,调用时间监控中心。(dubbo的控制台页面中能够显示)

Container:服务运行的容器。

调用关系:

  1.服务器负责启动,加载,运行提供者(例如在tomcat容器中,启动dubbo服务端)。

  2.提供者在启动时,向注册中心注册本身提供的服务。

  3.消费者启动时,向注册中心订阅本身所需的服务。

  4.注册中心返回提供者地址列表给消费者,若是有变动,注册中心将基于长链接推送变动数据给消费者。

  5.消费者,从远程接口列表中,调用远程接口,dubbo会基于负载均衡算法,选一台提供者进行调用,若是调用失败则选择另外一台.

dubbo主要核心部件

Remoting:网络通讯框架,实现了sync-over-asyncrequest-response消息机制。

RPC:一个远程过程调用的抽象,支持负载均衡、容灾和集群功能。

Registry:服务目录框架用于服务的注册和服务事件发布和订阅。(相似第一篇文章中的点菜宝)

7.1.5 dubbo直连、只订阅、只注册

1 直连(适用开发)

  在开发及测试环境下,常常须要绕过注册中心,只测试指定服务提供者,这时候可能须要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A接口配置点对点,不影响B接口从注册中心获取列表。

  <dubbo:reference interface="com.changhf.service.DeptmentService" id="deptmentService" check="false"  url="dubbo://192.168.1.1:20881"/>

2 只订阅

  为方便开发测试,常常会在线下共用一个全部服务可用的注册中心,这时,若是一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。

解决方案:

      可让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,经过直连测试正在开发的服务。

<dubbo:registry protocol="zookeeper" address="${dubbo.registry.address}" register="false"/>  

3 只注册

   若是有两个镜像环境(例如环境AB),两个注册中心,有一个服务(例如D)只在其中一个注册中心有部署,另外一个注册中心还没来得及部署,而两个注册中心的其它应用都须要依赖此服务,因此须要将服务同时注册到两个注册中心,但却不能让此服务同时依赖两个注册中心的其它服务(其它服务:例如服务A)

解决方案:

      可让服务提供者方,只注册服务到另外一注册中心,而不从另外一注册中心订阅服务。

<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />

<dubbo:registry id="qdRegistry" address="10.20.141.150:9090" subscribe="false" />

或:

<dubbo:registry id="hzRegistry" address="10.20.153.10:9090" />

<dubbo:registry id="qdRegistry" address="10.20.141.150:9090?subscribe=false" />

 

 

 

8 Netty

8.1.1 BIONIOAIO的区别?

BIO:一个链接一个线程,客户端有链接请求时服务器端就须要启动一个线程进行处理。线程开销大。

伪异步IO:将请求链接放入线程池,一对多,但线程仍是很宝贵的资源。

NIO:一个请求一个线程,但客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。

AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,

BIO是面向流的,NIO是面向缓冲区的;BIO的各类流是阻塞的。而NIO是非阻塞的;BIOStream是单向的,而NIOchannel是双向的。

 

注意:Java NIO(New IO)Linux NIO(non-blocking IO)不同,Java NIO多路复用IO模型。

下面这张图就表示了五种Linux IO模型的处理流程:

 

BIO(blocking IO): 同步阻塞IO,阻塞整个步骤,若是链接少,他的延迟是最低的,由于一个线程只处理一 个链接,适用于少链接且延迟低的场景,好比说数据库链接。

NIO(non-blocking IO): 同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景, 好比聊天软件,注意这里所说的NIO并不是JavaNIONew IO)库

多路复用IO: 他的两个步骤处理是分开的,也就是说,一个链接可能他的数据接收是线程a完成的, 数据处理是线程b完成的。相比NIO(多线程+ BIO)链接处理量越大越有优点。即经典的 Reactor设计模式,Java中的SelectorLinux中的epoll都是这种模型。

信号驱动IO: 这种IO模型主要用在嵌入式开发,不参与讨论。

异步IO: 他的数据请求和数据处理都是异步的,数据请求一次返回一次,适用于长链接的业务场景。即经 典的Proactor设计模式,也称为异步非阻塞IO

 

Reactor模式中,事件分发器等待某个事件或者可应用或个操做的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来作实际的读写操做。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操做,处理读到的数据,注册新的事件,而后返还控制权。

8.1.2 同步IO和异步IO的区别?

 

上图对于一次NIO访问(以read举例),数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。因此说,当一个read操做发生时,它会经历两个阶段:

1. 等待数据准备 (Waiting for the data to be ready)

2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

 

同步:无论是BIO,NIO,仍是IO多路复用,第二步数据从内核缓存写入用户缓存必定是由用户线程自行读  取数据,处理数据。

异步:第二步数据是内核写入的,并放在了用户线程指定的缓存区,写入完毕后通知用户线程。

8.1.3 Netty的线程模型?

Netty经过Reactor模型基于多路复用器接收并处理用户请求,内部实现了两个线程池,boss线程池和work线程池,其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的readwrite事件,由对应的Handler处理。

单线程模型:全部I/O操做都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的。既要接收客户端的链接请求,向服务端发起链接,又要发送/读取请求或应答/响应消息。一个NIO 线程同时处理成百上千的链路,性能上没法支撑,速度慢,若线程进入死循环,整个程序不可用,对于高负载、大并发的应用场景不合适。

多线程模型:有一个NIO 线程(Acceptor) 只负责监听服务端,接收客户端的TCP 链接请求;NIO 线程池负责网络IO 的操做,即消息的读取、解码、编码和发送;1 NIO 线程能够同时处理N 条链路,可是1 个链路只对应1 NIO 线程,这是为了防止发生并发操做问题。但在并发百万客户端链接或须要安全认证时,一个Acceptor 线程可能会存在性能不足问题。

主从多线程模型:Acceptor 线程用于绑定监听端口,接收客户端链接,将SocketChannel 从主线程池的Reactor 线程的多路复用器上移除,从新注册到Sub 线程池的线程上,用于处理I/O 的读写等操做,从而保证mainReactor只负责接入认证、握手等操做;

8.1.4 Netty零拷贝

Linux 操做系统的标准 I/O 接口是基于数据拷贝操做的,即 I/O 操做会致使数据在操做系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样作最大的好处是能够减小磁盘 I/O 的操做,由于若是所请求的数据已经存放在操做系统的高速缓冲存储器中,那么就不须要再进行实际的物理磁盘 I/O 操做。可是数据传输过程当中的数据拷贝操做却致使了极大的 CPU 开销,限制了操做系统有效进行数据传输操做的能力。

零拷贝( zero-copy )这种技术能够有效地改善数据传输的性能,在内核驱动程序(好比网络堆栈或者磁盘存储驱动程序)处理 I/O 数据的时候,零拷贝技术能够在某种程度上减小甚至彻底避免没必要要 CPU 数据拷贝操做。现代的 CPU 和存储体系结构提供了不少特征能够有效地实现零拷贝技术,可是由于存储体系结构很是复杂,并且网络协议栈有时须要对数据进行必要的处理,因此零拷贝技术有可能会产生不少负面的影响,甚至会致使零拷贝技术自身的优势彻底丧失。

 

Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,通常咱们的数据若是须要从IO读取到堆内存,中间须要通过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,若是数据量大,就会形成没必要要的资源浪费。

 

零拷贝,当他须要接收数据的时候,他会在堆内存以外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面经过ByteBuf能够直接对这些数据进行直接操做,从而加快了传输速度。

 

 

 

9 Tomcat

9.1.1 Tomcat内存优化

Tomcat内存优化主要是对 tomcat 启动参数优化,咱们能够在 tomcat 的启动脚本 catalina.sh 中设置 java_OPTS 参数。

  JAVA_OPTS参数说明

  -server 启用jdk server 版;

  -Xms java虚拟机初始化时的最小内存;

  -Xmx java虚拟机可以使用的最大内存;

  -XX: PermSize 内存永久保留区域

-XX:MaxPermSize 内存最大永久保留区域

服务器参数配置

现公司服务器内存通常均可以加到最大2G ,因此能够采起如下配置:

JAVA_OPTS=-Xms1024m -Xmx2048m -XX: PermSize=256M -XX:MaxNewSize=256m -XX:MaxPermSize=256m

配置完成后可重启Tomcat ,经过如下命令进行查看配置是否生效:

首先查看Tomcat 进程号:

udo lsof -i:9027

咱们能够看到Tomcat 进程号是 12222

查看是否配置生效:

sudo jmap heap 12222

咱们能够看到MaxHeapSize 等参数已经生效。

9.1.1 Tomcat并发优化

1.Tomcat链接相关参数

  在Tomcat 配置文件 server.xml 中的

  <Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" minProcessors="100"

maxProcessors="1000"  acceptCount="1000" redirectPort="8443"

2.调整链接器connector的并发处理能力

  1>参数说明

   maxThreads 客户请求最大线程数

   minSpareThreads Tomcat初始化时建立的 socket 线程数

   maxSpareThreads Tomcat链接器的最大空闲 socket 线程数

   enableLookups 若设为true, 则支持域名解析,可把 ip 地址解析为主机名

   redirectPort 在须要基于安全通道的场合,把客户请求转发到基于SSL redirectPort 端口

   acceptAccount 监听端口队列最大数,满了以后客户请求会被拒绝(不能小于maxSpareThreads

   connectionTimeout 链接超时

   minProcessors 服务器建立时的最小处理线程数

   maxProcessors 服务器同时最大处理线程数

   URIEncoding URL统一编码

  2>Tomcat中的配置示例

   <Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" maxThreads="1000"

minSpareThreads="100" maxSpareThreads="1000" minProcessors="100" maxProcessors="1000"

enableLookups="false" URIEncoding="utf-8" acceptCount="1000" 

redirectPort="8443" disableUploadTimeout="true"/>

        

9.1.1 Tomcat缓存优化

1参数说明

  c ompression 打开压缩功能

  compressionMinSize 启用压缩的输出内容大小,这里面默认为2KB

  compressableMimeType 压缩类型

connectionTimeout 定义创建客户链接超时的时间. 若是为 -1, 表示不限制创建客户链接的时间

2 Tomcat中的配置示例

<Connector port="9027" protocol="HTTP/1.1" maxHttpHeaderSize="8192" maxThreads="1000"

 minSpareThreads="100" maxSpareThreads="1000" minProcessors="100" maxProcessors="1000"

 enableLookups="false" compression="on" compressionMinSize="2048"

compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" 

 connectionTimeout="20000" URIEncoding="utf-8" acceptCount="1000" redirectPort="8443"

 disableUploadTimeout="true"/>             

      

 

 

11 mybatis

11.1.1 mybatis根据mapper接口如何生成实现类?

答案是mybatis经过JDK的动态代理方式,在启动加载配置文件时,根据配置mapperxml去生成。.

 

1、mapper代理类是如何生成的.

1.若是不是集成spring的,会去读取<mappers>节点,去加载mapperxml配置

  <mappers>

        <mapper resource="com/xixicat/dao/CommentMapper.xml"/>

    </mappers>

2.若是是集成spring的,会去读springsqlSessionFactoryxml配置中的mapperLocations,而后去解析mapperxml.

  <mappers>

        <mapper resource="com/xixicat/dao/CommentMapper.xml"/>

    </mappers>

3. 而后绑定namespace(XMLMapperBuilder)

private void bindMapperForNamespace() {

     String namespace = builderAssistant.getCurrentNamespace();

     if (namespace != null) {

      Class<?> boundType = null;

      try {

        boundType = Resources.classForName(namespace);

      } catch (ClassNotFoundException e) {

        //ignore, bound type is not required

      }

      if (boundType != null) {

        if (!configuration.hasMapper(boundType)) {

          // Spring may not know the real resource name so we set a flag

          // to prevent loading again this resource from the mapper interface

          // look at MapperAnnotationBuilder#loadXmlResource

          configuration.addLoadedResource("namespace:" + namespace);

          configuration.addMapper(boundType);

        }

      }

     }

  }

这里先去判断该namespace能不能找到对应的class,若能够则调用configuration.addMapper(boundType);

4. configuration委托给MapperRegistry:

  public <T> void addMapper(Class<T> type) {

    mapperRegistry.addMapper(type);

  }

5. 生成该mapper的代理工厂(MapperRegistry)

这里的重点就是MapperProxyFactory类:

6. getMapper的时候生成mapper代理类

  @SuppressWarnings("unchecked")

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

    if (mapperProxyFactory == null) {

      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

    }

    try {

      return mapperProxyFactory.newInstance(sqlSession);

    } catch (Exception e) {

      throw new BindingException("Error getting mapper instance. Cause: " + e, e);

    }

  }

10 并发优化相关

10.1.1 并发

最多见的就是多线程,尽量提升程序的并发度。

好比屡次rpc顺序调用,经过异步rpc转化为并发调用;

好比数据分片,你的一个Job要扫描全表,跑几个小时,数据分片,用多线程,性能会加快好几倍。

10.1.2 缓存

缓存你们都不陌生,遇到性能问题,你们首先想到的就是缓存。

关于缓存,一个关键点就是:缓存的粒度问题。

好比Tweet的架构,缓存的粒度从小到大,有Row Cache, Vector Cache, Fragment Cache, Page Cache

粒度越小,重用性越好,但查询须要屡次,须要数据拼装;

粒度越大,越容易会失效,任何一个小的地方改动,均可能形成缓存的失效。

10.1.3 批量

批量其实也是在线/离线的一种思想,把实时问题,转化为一个批量处理的问题,从而下降对系统吞吐量的压力

好比Kafka中的批量发消息;

好比广告扣费系统中,把屡次点击累积在一块儿扣费;

10.1.4 读写分离

一样,对传统的单机Mysql数据库,读和写是彻底同步的。写进去的内容,立马就能够读到。

但在不少业务场景下,读和写并不须要彻底同步。这个时候,就能够分开存储,写到一个地方,再异步的同步到另外一个地方。这样就能够实现读写分离。

好比MysqlMaster/Slave就是个典型,Slave上面的数据并非和Master实时同步的;

再好比各类报表分析,OLTP/OLAP,线上/线下数据分离,线上数据按期同步到Hive集群,再作分析

10.1.5 动静分离

动静分离的典型例子就是网站的前端,动态的页面,放在web服务器上;静态的css/jss/img,直接放到CDN上,这样既提升性能,也极大的下降服务器压力。

按照这个思路,不少大型网站都致力于动态内容的静态化,静态化以后,就能够很容易的缓存。

10.1.6冷热分离(冷数据备份)

好比按期把mysql中的历史数据作备份到离线数据库等。

10.1.7服务熔断与降级

服务降级是系统的最后一道保险。在一个复杂系统内部,一个系统每每会调用其它很大系统的服务。在大流量的状况下,咱们可能会在保证主流程能正常工做的状况下,对其它服务作降级。

所谓降级,也就是当某个服务不可用时,干脆就别让其提供服务了,直接返回一个缺省的结果。虽然这个服务不可用,但它不至于让整个主流程瘫痪,这就能够最大限度的保证核心系统可用。

10.1.8最终一致性

在分布式系统中,由于数据的分拆,服务的分拆,强一致性就很难保证。这个时候,用的最多的就是最终一致性

强一致性,弱一致性,最终一致性,是一致性的几个不一样的等级。在传统的关系型数据库中,经过事务来保证强一致性。

但在分布式系统中,一般都会把强一致性折中成最终一致性,从而变相的解决分布式事务问题。

典型的转账的例子,AB转账1万块钱,A的帐号扣1万,B的帐号加1万。但这2步未必须要同时发生, A的扣完以后,B的帐号上面未必立马就有,但只要保证B最终能够收到就能够了。

最终一致性的实现,一般都须要一个高可靠的消息队列。

 

10.1.9线程池

线程池的做用:

线程池做用就是限制系统中执行线程的数量。

  根据系统的环境状况,能够自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了形成系统拥挤效率不高。用线程池控制线程数量,其余线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务须要运行时,若是线程池中有等待的工做线程,就能够开始运行了;不然进入等待队列。

为何要用线程池:

1.减小了建立和销毁线程的次数,每一个工做线程均可以被重复利用,可执行多个任务。

2.能够根据系统的承受能力,调整线程池中工做线线程的数目,防止由于消耗过多的内存,而把服务器累趴下(每一个线程须要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

Java里面线程池的顶级接口是Executor,可是严格意义上讲Executor并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService

10.2.0 在线计算 vs. 离线计算 / 同步 vs. 异步

在实际的业务需求中,并非全部须要都须要彻底实时的:

好比内部针对产品、运营开发的各类报表查询、分析系统;

好比微博的传播,我发了一个微博,个人粉丝延迟几秒才看到,这是能够接受的,由于他并不会注意到晚了几秒;

好比搜索引擎的索引,我发了一篇博客,可能几分钟以后,才会被搜索引擎索引到;

好比支付宝转账、提现,也并不是这边转出以后,对方当即收到;

。。。

这类例子不少。这种“非实时也能够接受“的场景,就为架构的设计赢得了充分的回旋余地。

由于非实时,咱们就能够作异步,好比使用消息队列,好比使用后台的Job,周期性处理某类任务;

也由于非实时,咱们能够作读写分离,读和写不是彻底同步,好比MysqlMaster-Slave

10.2.1计算分拆

计算的分拆有2种思路:

数据分拆:一个大的数据集,拆分红多个小的数据集,并行计算。

好比大规模数据归并排序

任务分拆:把一个长的任务,拆分红几个环节,各个环节并行计算。

Java中多线程的Fork/Join框架,Hadoop中的Map/Reduce,都是计算分拆的典型框架。其思路都是类似的,先分拆计算,再合并结果。

再好比分布式的搜索引擎中,数据分拆,分别建索引,查询结果再合并。

 

 

 

11 java经常使用设计模式

11.1.1 适配器模式

适配器模式:将一个类的接口转换成客户但愿的另一个接口。适配器模式使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。

11.1.2 装饰者模式

装饰者模式:动态给类加功能。

11.1.3观察者模式

观察者模式:有时被称做发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知全部观察者对象,使它们可以自动更新本身。

简单的例子就是一个天气系统,当天气变化时必须在展现给公众的视图中进行反映。这个视图对象是一个主体,而不一样的视图是观察者。

11.1.4策略模式

策略模式:定义一系列的算法,把它们一个个封装起来, 而且使它们可相互替换。

11.1.5外观模式

外观模式:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

11.1.6命令模式

命令模式:将一个请求封装成一个对象,从而使您能够用不一样的请求对客户进行参数化。

11.1.7建立者模式

建立者模式:将一个复杂的构建与其表示相分离,使得一样的构建过程能够建立不一样的表示。

11.1.8抽象工厂模式

抽象工厂模式:提供一个建立一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

12 zookeeper

12.1.1 Zookeeper是什么框架

分布式的、开源的分布式应用程序协调服务,本来是HadoopHBase的一个重要组件。它为分布式应用提供一致性服务的软件,包括:配置维护、域名服务、分布式同步、组服务等

12.1.2 ZooKeeper的三种角色

群首(leader),追随者(follower),观察者(observer)。

Leader做为整个ZooKeeper集群的主节点,负责响应全部对ZooKeeper状态变动的请求。它会将每一个状态更新请求进行排序和编号,以便保证整个集群内部消息处理的FIFO

事物请求的惟一调度和处理者,保证集群事务处理的顺序性。

集群内部各个服务器的调度者。

 

Follower

处理客户端非事物请求,转发事物请求给Leader服务器。

参与事务请求Proposal的投票。

参与Leader选举投票。

 

Observer

Follower惟一的区别在于,Observer不参与任何形式的投票,包括事物请求Proposal的投票和Leader选举投票。简单地讲,Observer服务器只提供非事物服务,一般用于在不影响集群事务处理能力的前提下提高集群的非事物处理能力

12.1.3 Zookeeper选举算法

对于zk系统的数据都是保存在内存里面的,一样也会备份一份在磁盘上。对于每一个zk节点而言,能够看作每一个zk节点的命名空间是同样的,也就是有一样的数据(下面的树结构)若是Leader挂了,zk集群会从新选举,在毫秒级别就会从新选举出一个Leaer集群中除非有一半以上的zk节点挂了,zk service才不可用。

其默认选举算法为FastLeaderElectionFastLeaderElection的本质是类fast Paxos的算法。Google Chubby的做者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。

如下给出FastLeaderElection算法的分析过程:

首先给出几个名词解释:

Serverid:在配置server时,给定的服务器的标示id

Zxid:服务器在运行时产生的数据idzxid越大,表示数据越新。

Epoch:选举的轮数,即逻辑时钟。

Server状态:LOOKING,FOLLOWING,OBSERVING,LEADING

总结:

1、首先开始选举阶段,每一个Server读取自身的zxid

2、发送投票信息

   a、首先,每一个Server第一轮都会投票给本身。

   b、投票信息包含 :所选举leaderServeridZxidEpochEpoch会随着选举轮数的增长而递增。

3、接收投票信息

1、若是所接收数据中服务器的状态是否处于选举阶段(LOOKING 状态)

首先,判断逻辑时钟值:

a)若是发送过来的逻辑时钟Epoch大于目前的逻辑时钟。首先,更新本逻辑时钟Epoch,同时清空本轮逻辑时钟收集到的来自其余server的选举数据。而后,判断是否须要更新当前本身的选举leader Serverid。判断规则rules judging:保存的zxid最大值和leader Serverid来进行判断的。先看数据zxid,数据zxid大者胜出;其次再判断leader Serverid,leader Serverid大者胜出;而后再将自身最新的选举结果(也就是上面提到的三种数据(leader ServeridZxidEpoch)广播给其余server)

b)若是发送过来的逻辑时钟Epoch小于目前的逻辑时钟。说明对方server在一个相对较早的Epoch中,这里只须要将本机的三种数据(leader ServeridZxidEpoch)发送过去就行。

c)若是发送过来的逻辑时钟Epoch等于目前的逻辑时钟。再根据上述判断规则rules judging来选举leader ,而后再将自身最新的选举结果(也就是上面提到的三种数据(leader  ServeridZxidEpoch)广播给其余server)。其次,判断服务器是否是已经收集到了全部服务器的选举状态:如果,根据选举结果设置本身的角色(FOLLOWING仍是LEADER),退出选举过程就是了。

最后,若没有收到没有收集到全部服务器的选举状态:也能够判断一下根据以上过程以后最新的选举leader是否是获得了超过半数以上服务器的支持,若是是,那么尝试在200ms内接收一下数据,若是没有新的数据到来,说明你们都已经默认了这个结果,一样也设置角色退出选举过程。

2、 若是所接收服务器不在选举状态,也就是在FOLLOWING或者LEADING状态。

a)逻辑时钟Epoch等于目前的逻辑时钟,将该数据保存到recvset。此时Server已经处于LEADING状态,说明此时这个server已经投票选出结果。若此时这个接收服务器宣称本身是leader, 那么将判断是否是有半数以上的服务器选举它,若是是则设置选举状态退出选举过程。

b) 不然这是一条与当前逻辑时钟不符合的消息,那么说明在另外一个选举过程当中已经有了选举结果,因而将该选举结果加入到outofelection集合中,再根据outofelection来判断是否能够结束选举,若是能够也是保存逻辑时钟,设置选举状态,退出选举过程。

 

 

13 http协议

13.1.1 什么是HTTP协议?

HTTP:超文本传输协议。使用的是可靠的数据传输协议,在传输的过程当中不会被损坏或产生混乱。HTTP能够从遍及全世界的Web服务器商将各类信息块迅速、便捷、可靠地搬移到人们桌面上的Web浏览器上去。

13.1.2 什么是URI

URI:统一资源标识符,在世界范围内惟一标识并定位信息资源。

URI有两种形式:URLURN

13.1.3 常见的状态码200206302304404503的含义?

200  成功。请求的全部数据都在响应主体中。

206  成功执行了一个部分或Range(范围)请求。206响应中必须包含Content-RangeDate以及ETagContent-Location首部。断点续传必考题。

302  重定向。到其余地方去获取资源。客户端应该是用使用Location首部给出的URL来临时定位资源。未来的请求仍应使用老的URL

304  若是客户端发起了一个GET请求,而资源最近未被修改,则用304说明资源未被修改。带有这个状态吗的响应不该该包含实体的主体部分。缓存必考题。

305  用来讲明必须经过一个代理来访问资源;代理的位置由Locatin首部给出。

403  请求被服务器拒绝了

404  没法找到所请求的URL

500  服务器遇到一个妨碍它为请求提供服务的错误。

503  服务器如今没法为请求提供服务,但未来能够。

13.1.4 什么是报文?

HTTP报文是由一行一行的简单的字符串组成的。HTTP报文都是纯文本,不是二进制代码。

请求报文:从Web客户端发往Web服务器的HTTP报文称为请求报文。

响应报文:从Web服务器发往客户端的报文称为响应报文。

HTTP报文包含如下三个部分:

起始行:报文的第一行就是起始行,在请求报文中用来讲明要作些什么,在响应报文中说明出现了什么状况。如:GET /jackson0714/p/algorithm_1.html HTTP/1.1

首部字段:起始行后面由零个或多个首部字段。以键值对的形式表示首部字段。键和值之间用冒号分隔。首部以一个空行结束。如Content-Typetext/html:charset=utf-8

主体:首部字段空行以后就是可选的报文主体了,其中包含了全部类型的数据。请求主体中包括了要发送Web服务器的数据,响应主体中装载了要返回给客户端的数据。

13.1.5 什么是dns

域名解析服务。将主机名转换为IP地址。如将http://www.cnblogs.com/主机名转换为IP地址:211.137.51.78

13.1.6 在浏览器地址栏输入一个HTTPURL地址,按下回车键以后,浏览器怎么经过HTTP显示位于远端服务器中的某个简单HTML资源?

1)浏览器从URL中解析出服务器的主机名;

2)浏览器将服务器的主机名转换成服务器的IP地址;

3)浏览器将端口号(若是有的话),从URL中解析出来;

4)浏览器创建一条与Web服务器的TCP链接;

5)浏览器向服务器发送一条HTTP请求报文;

6)服务器向浏览器回送一条HTTP响应报文;

7)关闭链接,浏览器显示文档。

13.1.7 HTTP协议栈是怎样的?

HTTP是应用层协议。它把联网的细节都交给了通用、可靠的因特网传输协议TCP\IP协议。

HTTP网络协议栈:

HTTP 应用层、 TCP 传输层、 IP 网络层、 网络特有的链路接口 数据链路层、 物理网络硬件 物理层

13.18 HttpHttps的区别:

1. HTTP URL http:// 开头,而HTTPS URL https:// 开头

2. HTTP 是不安全的,而 HTTPS 是安全的

3. HTTP 标准端口是80 ,而 HTTPS 的标准端口是443

4. OSI 网络模型中,HTTP工做于应用层,而HTTPS 的安全传输机制工做在传输层

5. HTTP 没法加密,而HTTPS 对传输的数据进行加密

6. HTTP无需证书,而HTTPS 须要CA机构wosign的颁发的SSL证书

13.19 HTTPS工做原理

1、首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;

2、客户端若是校验经过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);

3、消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就获得了RSA签名;

4、发送给服务端,此时只有服务端(RSA私钥)能解密。

5、解密获得的随机数,再用AES加密,做为密钥(此时的密钥只有客户端和服务端知道)。

13.20 什么是HTTPS

在说HTTPS以前先说说什么是HTTPHTTP就是咱们平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的,也就是明文的,所以使用HTTP协议传输隐私信息很是不安全。为了保证这些隐私数据能加密传输,因而网景公司设计了SSLSecure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPSSSL目前的版本是3.0,被IETFInternet Engineering Task Force)定义在RFC 6101中,以后IETFSSL 3.0进行了升级,因而出现了TLSTransport Layer Security1.0,定义在RFC 2246。实际上咱们如今的HTTPS都是用的TLS协议,可是因为SSL出现的时间比较早,而且依旧被如今浏览器所支持,所以SSL依然是HTTPS的代名词,但不管是TLS仍是SSL都是上个世纪的事情,SSL最后一个版本是3.0,从此TLS将会继承SSL优良血统继续为咱们进行加密服务。目前TLS的版本是1.2,定义在RFC 5246中,暂时尚未被普遍的使用。

 

 

 

 

 

 

14 java网络编程

14.1.1 网络编程时的同步、异步、阻塞、非阻塞?

同步:函数调用在没获得结果以前,没有调用结果,不返回任何结果。

异步:函数调用在没获得结果以前,没有调用结果,返回状态信息。

阻塞:函数调用在没获得结果以前,当前线程挂起。获得结果后才返回。

非阻塞:函数调用在没获得结果以前,当前线程不会挂起,当即返回结果。

141.2 什么状况下须要序列化?序列化的注意事项,如何实现java 序列化(串行化)

当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;

· 当你想用套接字在网络上传送对象的时候;

· 当你想经过RMI传输对象的时候;

序列化注意事项:

1、若是子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,不然会抛InvalidClassException异常。

2、静态变量不会被序列化,那是类的“菜”,不是对象的。串行化保存的是对象的状态,即非静态的属性,即实例变量。不能保存类变量。

3transient关键字修饰变量能够限制序列化。对于不须要或不该该保存的属性,应加上transient修饰符。要串行化的对象的类必须是公开的(public)。

4、虚拟机是否容许反序列化,不只取决于类路径和功能代码是否一致,一个很是重要的一点是两个类的序列化 ID是否一致,就是 private static final long serialVersionUID = 1L

5Java 序列化机制为了节省磁盘空间,具备特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。反序列化时,恢复引用关系。

6、序列化到同一个文件时,如第二次修改了相同对象属性值再次保存时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,所以只保存第二次写的引用,因此读取时,都是第一次保存的对象。

141.3 java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

JDK提供的流继承了四大类:

InputStream(字节输入流)OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)。

按流向分类:

输入流: 程序能够从中读取数据的流。

输出流: 程序能向其中写入数据的流。

按数据传输单位分类:

字节流:以字节(8位二进制)为单位进行处理。主要用于读写诸如图像或声音的二进制数据。

字符流:以字符(16位二进制)为单位进行处理。

都是经过字节流的方式实现的。字符流是对字节流进行了封装,方便操做。在最底层,全部的输入输出都是字节形式的。

后缀是Stream是字节流,然后缀是ReaderWriter是字符流。

按功能分类:

节点流:从特定的地方读写的流类,如磁盘或者一块内存区域。

过滤流:使用节点流做为输入或输出。过滤流是使用一个已经存在的输入流或者输出流链接建立的。

141.4 TCP三次握手

Client

Server

所谓三次握手(Three-Way Handshake)即创建TCP链接,就是指创建一个TCP链接时,须要客户端和服务端总共发送3个包以确认链接的创建。在socket编程中,这一过程由客户端执行connect来触发,整个流程以下图所示:

 

 

 

 

 

 

 

 

 

1)第一次握手:

Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给ServerClient进入SYN_SENT状态,等待Server确认。

2)第二次握手:

Server收到数据包后由标志位SYN=1知道Client请求创建链接,Server将标志位SYNACK都置为1ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认链接请求,Server进入SYN_RCVD状态。

3)第三次握手:

Client收到确认后,检查ack是否为J+1ACK是否为1,若是正确则将标志位ACK置为1ack=K+1,并将该数据包发送给ServerServer检查ack是否为K+1ACK是否为1,若是正确则链接创建成功,ClientServer进入ESTABLISHED状态,完成三次握手,随后ClientServer之间能够开始传输数据了。

SYN攻击:

在三次握手过程当中,Server发送SYN-ACK以后,收到ClientACK以前的TCP链接称为半链接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短期内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,因为源地址是不存在的,所以,Server须要不断重发直至超时,这些伪造的SYN包将产时间占用未链接队列,致使正常的SYN请求由于队列满而被丢弃,从而引发网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式很是简单,即当Server上有大量半链接状态且源IP地址是随机的,则能够判定遭到SYN攻击了,使用以下命令可让之现行:

#netstat -nap | grep SYN_RECV

141.5 TCP四次挥手

三次握手耳熟能详,四次挥手估计就少有人知道了。所谓四次挥手(Four-Way Wavehand)即终止TCP链接,就是指断开一个TCP链接时,须要客户端和服务端总共发送4个包以确认链接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程以下图所示:

Client

Server

 

 

 

 

 

 

 

 

 

 

 

 

因为TCP链接时全双工的,所以,每一个方向都必需要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的链接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,可是在这个TCP链接上仍然可以发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另外一方则执行被动关闭,上图描述的便是如此。

1TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。

2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN同样,一个FIN将占用一个序号。

3) 服务器关闭客户端的链接,发送一个FIN给客户端(报文段6)。

4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

141.6 TCP报文格式

1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1

3)标志位:共6个,即URGACKPSHRSTSYNFIN等,具体含义以下:

       AURG:紧急指针(urgent pointer)有效。

       BACK:确认序号有效。

       CPSH:接收方应该尽快将这个报文交给应用层。

       DRST:重置链接。

       ESYN:发起一个新链接。

       FFIN:释放一个链接。

须要注意的是:

A)不要将确认序号Ack与标志位中的ACK搞混了。

B)确认方Ack=发起方Req+1,两端配对。

相关文章
相关标签/搜索