精华内容
下载资源
问答
  • Java面试核心知识点

    2020-01-30 19:50:10
    from &...1. 目录 文章目录1. 目录2. JVM2.1. 线程2.2. JVM 内存区域2.3. JVM 运行时内存2.4. 垃圾回收与算法2.5. JAVA 四中引用类型2.6. GC 分代收集算法 VS 分区收集算法2.7. GC 垃圾收集器2.8. JA...

    from & refer: https://download.csdn.net/download/zhiyuan411/12085383

    1. 目录

    文章目录

    2. JVM

    2.1. 线程

    Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系。

    2.2. JVM 内存区域

    线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁。
    线程共享区域随虚拟机的启动/关闭而创建/销毁。

    2.2.1. 程序计数器(线程私有)
    2.2.2. 虚拟机栈(线程私有)
    2.2.3. 本地方法区(线程私有)
    本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务。
    HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。
    2.2.4. 堆(Heap-线程共享)-运行时数据区
    2.2.5. 方法区/永久代(线程共享)

    2.3. JVM 运行时内存

    2.3.1. 新生代
    是用来存放新生的对象。一般占据堆的 1/3 空间。
    由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。
    新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

    2.3.1.1. Eden 区
    Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。
    当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。

    2.3.1.2. ServivorFrom
    上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

    2.3.1.3. ServivorTo
    保留了一次 MinorGC 过程中的幸存者。

    2.3.1.4. MinorGC 的过程(复制->清空->互换)
    MinorGC 采用复制算法。

    2.3.2 老年代
    主要存放应用程序中生命周期长的内存对象。
    老年代的对象比较稳定,所以 MajorGC 不会频繁执行。
    MajorGC 采用标记清除算法。MajorGC 的耗时比较长,还会产生内存碎片。
    当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。

    2.3.3 永久代
    指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。
    所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

    在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
    元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。

    2.4. 垃圾回收与算法

    2.4.1. 如何确定垃圾
    引用计数法:一个对象如果没有任何与之关联的引用,即他们的引用计数为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
    可达性分析:用于解决引用计数法的循环引用问题。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。两次标记后仍然是不可达对象,则将面临回收。

    2.4.2. 标记清除算法(Mark-Sweep)
    标记阶段标记出所有需要回收的对象,清 除阶段回收被标记的对象所占用的空间。该算法最大的问题就是内存碎片化严重。

    2.4.3. 复制算法(copying)
    按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉。
    该算法最大的问题是可用内存被压缩到了原 本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

    2.4.4. 标记整理算法(Mark-Compact)
    标记阶段和 Mark-Sweep 算法相同,标记后不是清 理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。

    2.4.5. 分代收集算法
    分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存 划分为不同的域,不同区域选择不同的算法。
    一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。
    新生代都采取 Copying 算法。老年代采用 Mark-Compact 算法。

    2.5. JAVA 四中引用类型

    2.5.1. 强引用
    最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。

    2.5.2. 软引用
    软引用需要用 SoftReference 类来实现。

    2.5.3. 弱引用
    弱引用需要用 WeakReference 类来实现。

    2.5.4. 虚引用
    虚引用需要 PhantomReference 类来实现。

    2.6. GC 分代收集算法 VS 分区收集算法

    当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法。
    分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间, 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。

    2.7. GC 垃圾收集器

    java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器。

    2.7.1. Serial 垃圾收集器(单线程、复制算法)
    2.7.2. ParNew垃圾收集器(Serial+多线程)
    2.7.3. Parallel Scavenge 收集器(多线程复制算法、高效、自适应调节策略)
    2.7.4. SerialOld收集器(单线程标记整理算法)
    2.7.5. ParallelOld收集器(多线程标记整理算法)
    2.7.6. CMS收集器(多线程标记清除算法、最短垃圾回收停顿时间)
    2.7.7. G1收集器(标记整理算法、低停顿垃圾回收)

    2.8. JAVA IO/NIO

    2.8.1. 阻塞 IO 模型
    读写数据过程中用户线程会发生阻塞现象。

    2.8.2. 非阻塞 IO 模型
    用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 IO 不会交出 CPU,而会一直占用 CPU。

    2.8.3. 多路复用 IO 模型
    多路复用 IO 模型是目前使用得比较多的模型。Java NIO 实际上就是多路复用 IO。
    有一个线程(在内核进行)不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作。

    2.8.4. 信号驱动 IO 模型
    当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函 数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到 信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。

    2.8.5. 异步 IO 模型
    异步 IO 模型才是最理想的 IO 模型。异步 IO 是需要操作系统的底层支持,在 Java 7 中,提供了 Asynchronous IO。
    用户线程只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了。

    2.8.6. JAVA IO包
    字节流使用InputStream/OutputStream及其子类来处理;字符流使用Reader/Writer及其子类来处理。

    2.8.7. JAVA NIO包
    NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。
    NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开, 数据到达)。

    NIO 和传统 IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的。

    2.9. JVM 类加载机制

    JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。

    • 加载阶段会在内存中生成一个代表这个类的 java.lang.Class 对 象,作为方法区这个类的各种数据的入口。可以从class文件,war包等处进行加载。在加载阶段可以自定义类加载 器以外
    • 验证阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
    • 准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
    • 解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引 用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中,不一定在内存中存在。直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄,一定在内存中存在。
    • 到了初始阶段,才开始真正执行类中定义的 Java 程序代码。执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。

    虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类,JVM 提 供了 3 种类加载器,从上到下依次为: 启动类加载器(Bootstrap ClassLoader,JAVA_HOME\lib 目录),扩展类加载器(Extension ClassLoader,JAVA_HOME\lib\ext 目录),应用程序类加载器(Application ClassLoader,用户路径(classpath)上的类库)。
    我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。

    JVM 通过双亲委派模型进行类的加载:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此。只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

    OSGI(Open Service Gateway Initiative),是面向 Java 的动态模型系统,可以实现模块级 的热插拔功能,同时也引入了额外的复杂度,因为它不遵守了类加载的双亲委托模型。

    3. JAVA集合

    3.1. 接口继承关系和实现

    集合类存放于 Java.util 包中,主要有 3 种:

    • Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
    • Iterator:迭代器,可以通过迭代器遍历集合中的数据
    • Map:是映射表的基础接口

    3.2. LIST

    3.2.1. ArrayList(数组)
    3.2.2. Vector(数组实现、线程同步)
    3.2.3. LinkList(链表)

    3.3. SET

    3.3.1.1. HashSet(Hash 表)
    3.3.1.2. TreeSet(二叉树)
    3.3.1.3. LinkHashSet(HashSet+LinkedHashMap)

    3.4. MAP

    3.4.1. HashMap(数组+链表+红黑树)
    Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。

    3.4.2. ConcurrentHashMap
    支持并发操作。
    ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁。每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以实现会更复杂。
    Java8 对 ConcurrentHashMap 进行了比较大的改动, Java8 也引入了红黑树。

    3.4.3. HashTable(线程安全)
    遗留类,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap。

    3.4.4. TreeMap(可排序)
    用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。

    3.4.5. LinkHashMap(记录插入顺序)
    用 Iterator 遍历LinkedHashMap 时,得到的记录是按照插入顺序排序的。

    4. JAVA 多线程并发

    4.1.1. JAVA 并发知识库

    同步调用,异步回调和Future模式

    • 同步调用,会顺序执行多个多线程任务;
    • 异步回调,在执行时是并行执行多个线程任务,多个线程任务的执行完成后也会异步调用回调函数来处理结果;
    • Future模式,启动时开始并行执行多个线程任务,在最后调用Future的get()获取结果时是同步阻塞方式等待返回结果。

    4.1.2. JAVA 线程实现/创建方式

    4.1.2.1. 继承 Thread 类
    Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。
    启动线程的方法就是通过 Thread 类的 start()实例方法,它将启动一个新线 程,并执行 run()方法。

    4.1.2.2. 实现 Runnable 接口
    启动 自定义的MyThread类,需要首先实例化一个 Thread,并传入自己的 MyThread 实例。

    4.1.2.3. ExecutorService、Callable<Class>、Future 有返回值线程
    有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。
    执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务 返回的 Object 了。

    4.1.2.4. 基于线程池的方式
    线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销 毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。

    4.1.3. 4 种线程池

    4.1.3.1. newCachedThreadPool
    调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。
    终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

    4.1.3.2. newFixedThreadPool
    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

    4.1.3.3. newScheduledThreadPool
    创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    4.1.3.4. newSingleThreadExecutor
    这个线程池只有一个线程, 并可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。

    4.1.4. 线程生命周期(状态)

    在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞 (Blocked)和死亡(Dead)5 种状态。
    启动后,线程状态也会多次在运行、阻塞之间切换。

    • 当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配 内存,并初始化其成员变量的值
    • 当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和 程序计数器,等待调度运行。
    • 如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状 态。
    • 阻塞状态是指线程因为 等待阻塞(o.wait->等待对列), 同步阻塞(lock->锁池), 或者 其他阻塞(sleep/join),暂时停止运行。
    • 线程正常/异常结束或调用stop()后就是死亡状态。

    4.1.5. 终止线程 4 种方式

    4.1.5.1. 正常运行结束
    4.1.5.2. 使用退出标志退出线程(volatile定义标志变量)
    4.1.5.3. Interrupt() 方法结束线程
    4.1.5.4. stop() 方法终止线程(线程不安全)

    4.1.6. sleep 与 wait 区别

    sleep()方法导致了程序暂停执行指定的时间,该方法是属于 Thread 类的,线程也不会释放对象锁。
    调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

    4.1.7. start 与 run 区别

    • start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,
      可以直接继续执行下面的代码。
    • 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态。
    • run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态。

    4.1.8. JAVA 后台线程

    守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公共服务,它的优先级比较低,在没有用户线程可服务时会自动离开(线程则是 JVM 级别的)。
    通过 setDaemon(true)来设置一个用户线程为“守护线程”;在 Daemon 线程中产生的新线程也是 Daemon 的。

    4.1.9. JAVA 锁

    4.1.9.1. 乐观锁
    读时不会上锁;写时会加锁:先读出当前版本号,然后比较跟上一次的版本号,如果一样则更新,失败则重复读-比较-写的操作。
    java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

    4.1.9.2. 悲观锁
    读时会加锁。
    java 中的悲观锁就是 Synchronized。AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,
    才会转换为悲观锁,如 RetreenLock。

    4.1.9.3. 自旋锁
    如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。 超过自旋等待的最大时间后进入阻塞状态。
    适用于锁的竞争不激烈,且占用锁时间非常短的代码块。
    在 JDK 1.6 引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及CPU负荷来决定。

    4.1.9.4. Synchronized 同步锁
    synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁,非公平锁。
    Java1.6,1.7 与 1.8 中,均对该关键字的实现机理做了优化。例如适应自旋、偏向锁和轻量级锁等,效率有了本质上的提高。

    4.1.9.5. ReentrantLock
    ReentrantLock 是一种独占式的可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。默认为非公平锁,可以初始化时设置是否公平锁。

    4.1.9.6. Semaphore 信号量
    Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
    号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。
    Semaphore 可以用来构建一些对象池,资源池之类的。计数为 1 的 Semaphore,可以作为一种类似互斥锁的机制。
    Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似。

    4.1.9.7. AtomicInteger
    AtomicInteger,是一个提供原子操作的 Integer 的类,常见的还有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通过 AtomicReference<V>将一个对象的所有操作转化成原子操作。这样使用更方便,效率也更高。

    4.1.9.8. 可重入锁(递归锁)
    可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

    4.1.9.9. 公平锁与非公平锁
    公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。
    非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。

    4.1.9.10. ReadWriteLock 读写锁
    读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。

    4.1.9.11. 共享锁和独占锁
    独占锁模式下,每次只能有一个线程能持有锁。
    共享锁则允许多个线程同时获取锁,并发访问 共享资源。

    4.1.9.12. 重量级锁(Mutex Lock)
    依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。因为操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,时间非常长。
    Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。

    4.1.9.13. 轻量级锁
    锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
    随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。

    轻量级锁不使用操作系统互斥量来实现,而是依赖多次 CAS 原子指令。
    轻量级锁并不是用来代替重量级锁的,它的本意是在线程交替执行同步块的前提下,减少传统的重量级锁使用产生的性能消耗。

    4.1.9.14. 偏向锁
    偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。
    引入偏向锁是为了在只有一个线程执行同步块的情况下尽量减少不必要的轻量级锁执行路径。

    4.1.9.15. 分段锁
    是一种思想,ConcurrentHashMap 是学习分段锁的最好实践。

    4.1.9.16. 锁优化

    • 减少锁持有时间:只用在有线程安全要求的程序上加锁。
    • 减小锁粒度: 将被很多线程访问的大对象拆成小对象,大大增加并行度,降低锁竞争。
    • 锁分离:例如读写锁。
    • 锁粗化:如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。
    • 锁消除:在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

    4.1.10. 线程基本方法

    4.1.10.1. 线程等待(wait)
    调用该方法的线程进入 WAITING 状态,并释放对象的锁。
    只有等待另外线程的通知或被中断才会返回。

    4.1.10.2. 线程睡眠(sleep)
    线程进入 TIMED-WATING 状态,并不释放当前占有的锁。

    4.1.10.3. 线程让步(yield)
    当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。

    4.1.10.4. 线程中断(interrupt)
    中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。对线程的行为影响会因为线程对该标识位处理方法的实现而不同。

    4.1.10.5. Join 等待其他线程终止
    等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,等到指定线程结束,当前线程再由阻塞状态变为就绪状态。
    很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法。

    4.1.10.7. 线程唤醒(notify)
    唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中任意一个线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
    类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。

    4.1.11. 线程上下文切换

    巧妙地利用了时间片轮转的方式, CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做上下文切换。
    上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行切换,上下文切换过程中的信息是保存在进程控制块(PCB, process control block)中的。PCB 还经常被称作“切换桢”(switchframe)。
    除了当前执行任务的时间片用完,IO阻塞,用户代码挂起当前任务,硬件中断等都会引起上下文切换。

    4.1.12. 同步锁与死锁

    4.1.13. 线程池原理

    线程池做的工作主要是:线程复用;控制最大并发数;管理线程。

    线程池的实现原理:继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以是阻塞的。

    4.1.14. JAVA 阻塞队列原理

    在阻塞队列中:
    当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放 入队列。
    当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有 空的位置,线程被自动唤醒。

    Java 中的阻塞队列:(无界队列是指队列没有固定大小,不存在队列满负荷情况)

    • ArrayBlockingQueue :由数组结构组成的有界阻塞队列。(公平、非公平)
    • LinkedBlockingQueue :由链表结构组成的有界阻塞队列。(两个独立锁提高并发、默认一个类似无限大小的容量Integer.MAX_VALUE)
    • PriorityBlockingQueue :支持优先级排序的无界阻塞队列。(compareTo 排序实现优先)
    • DelayQueue:使用优先级队列实现的无界阻塞队列。(缓存失效、定时任务 )
    • SynchronousQueue:不存储元素的阻塞队列。(不存储数据、可用于传递数据)
    • LinkedTransferQueue:由链表结构组成的无界阻塞队列。
    • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。(两端插入和移出)

    4.1.15. CyclicBarrier、CountDownLatch、Semaphore 的用法

    4.1.15.1. CountDownLatch(线程计数器 )
    4.1.15.2. CyclicBarrier(回环栅栏-调用await()等待至某个状态再全部同时执行,这个状态称为 barrier 状态)
    4.1.15.3. Semaphore(信号量-控制同时访问的线程个数)

    CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待。Semaphore更类似于锁,用于控制对某组资源的访问权限。

    4.1.16. volatile 关键字的作用(变量可见性、禁止重排序)

    volatile 变量具备两种特性:

    • volatile 变量不会被缓存在寄存器或者对多CPU的其他处理器不可见的 地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。
    • volatile 禁止了指令重排。

    volatile 变量是一 种比 sychronized 关键字更轻量级的同步机制。
    volatile 适合这种场景:一个变量被多个线程共 享,线程直接给这个变量赋值;且写操作不能依赖于当前值(如i++),不依赖于其他volatile变量。

    4.1.17. 如何在两个线程之间共享数据

    Java 里面进行多线程通信的主要方式就是共享内存的方式。
    共享内存要保证:可见性和有序性原子性。Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题。

    常规实现方法:

    • 将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和容易做到同步,只要在方法上加 synchronized。
    • 将 Runnable 对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个 Runnable 对象调用外部类的这些方法。

    4.1.18. ThreadLocal 作用(线程本地存储)

    ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

    4.1.19. synchronized 和 ReentrantLock 的区别

    • ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作(必须在 finally 控制块中手动进行解锁操),synchronized 会被 JVM 自动加锁与解锁。
    • ReentrantLock 是 API 级别的(JDK中实现),synchronized 是 JVM 级别的。
    • ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁。这种情况下需要使用 ReentrantLock。

    4.1.20. ConcurrentHashMap 并发

    4.1.21. Java 中用到的线程调度

    4.1.21.1. 抢占式调度
    抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制。一个线程的堵塞不会导致整个进程堵塞。

    4.1.21.2. 协同式调度
    协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但处理不当可能会导致整个进程堵塞。

    4.1.21.3. JVM 的线程调度实现(抢占式调度)
    java 使用的线程调使用抢占式调度,Java 中线程会按优先级分配 CPU 时间片运行。

    4.1.22. 进程调度算法

    4.1.22.1. 优先调度算法
    例如:先来先服务调度算法(FCFS),短作业(进程)优先调度算法

    4.1.22.2. 高优先权优先调度算法
    例如:非抢占式优先权算法,抢占式优先权调度算法,高响应比优先调度算法(作业的优先级随着等待时
    间的增加而提高)

    4.1.22.3. 基于时间片的轮转调度算法
    例如:时间片轮转法,多级反馈队列调度算法(设置多个就绪队列,并为各个队列赋予不同的优先级和时间片大小)

    4.1.23. 什么是CAS(比较并交换-乐观锁机制-锁自旋)

    CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:
    它包含 3 个参数 CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等于 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当 前线程什么都不做。最后,CAS 返回当前 V 的真实值。
    并且,通过版本号的方式来保证了完成读数据和比较并交换的操作的原子性。

    4.1.24. 什么是AQS(抽象的队列同步器)

    AbstractQueuedSynchronizer 类如其名,抽象的队列式的同步器,AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的 ReentrantLock/Semaphore/CountDownLatch。

    5. JAVA基础

    5.1.1. JAVA 异常分类及处理

    Throwable 是 Java 语言中所有错误或异常的超类。下一层分为 Error 和 Exception。
    Exception 又有两个分支,一个是运行时异常 RuntimeException(可能在 Java 虚拟机正常运行期间抛出的异常的超类),一个是 CheckedException(一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常)。

    5.1.2. JAVA 反射

    反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法。
    反序列化是基于反射实现的。

    // 获取 Person 类的 Class 对象
    // Person p=new Person();
    // Class clazz=p.getClass();
    // Class clazz=Person.class;
    // Class clazz=Class.forName("类的全路径"); (最常用,安全&性能最好)
    Class clazz=Class.forName("reflection.Person");
    // 获取 Person 类的所有方法信息
    Method[] method=clazz.getDeclaredMethods(); for(Method m:method){
    System.out.println(m.toString()); }
    // 获取 Person 类的所有成员属性信息
    Field[] field=clazz.getDeclaredFields(); for(Field f:field){
    System.out.println(f.toString()); }
    // 获取 Person 类的所有构造方法信息
    Constructor[] constructor=clazz.getDeclaredConstructors(); for(Constructor c:constructor){
    System.out.println(c.toString());
    }
    

    5.1.3. JAVA 注解

    Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation 对象,然后通过该Annotation 对象来获取注解中的元数据信息。

    元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。

    • @Target 定义修饰的对象范围
    • @Retention 定义被保留的时间长短,支持保留到源文件时,编译时,或者运行时
    • @Documented 描述-javadoc
    • @Inherited 阐述了某个被标注的类型是被继承的

    内置的3种标准注解:

    • @Override 表示当前的方法定义将覆盖超类中的方法。如果方法签名对不上被覆盖的方法,编译器就会发出错误提示。
    • @Deprecated 如果使用了注解为它的元素,那么编译器会发出警告信息。
    • @SuppressWarnings 关闭不当的编译器警告信息。

    自定义注解的实例:

    // 1: 定义注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    // 使用关键字 @interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口
    public @interface FruitProvider {
        public int id() default -1;
        public String name() default "";
        public String address() default "";
    }
    
    // 2: 使用注解
    public class Apple {
        @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
        private String appleProvider;
    
        public void setAppleProvider(String appleProvider) {
            this.appleProvider = appleProvider;
        }
    
        public String getAppleProvider() {
            return appleProvider;
        }
    }
    
    // 3: 解释注解(注解处理器)
    public class FruitInfoUtil {
        public static void getFruitInfo(Class<?> clazz) {
            String strFruitProvicer = "供应商信息:";
            //通过反射获取处理注解,因为我们的注解是定义在Filed上的
            Field[] fields = clazz.getDeclaredFields(); 
            for (Field field : fields) {
                if (field.isAnnotationPresent(FruitProvider.class)) {
                    FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class); //注解信息的处理地方
                    strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:"
                            + fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
                    System.out.println(strFruitProvicer);
                }
            }
        }
    }
    
    // 测试
    public class FruitRun {
        public static void main(String[] args) {
            FruitInfoUtil.getFruitInfo(Apple.class);
    /***********输出结果***************/
    // 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延
        }
    }
    

    5.1.4. JAVA 内部类

    根据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。

    5.1.4.1. 静态内部类

    • HashMap 内部就有一个静态内部类 Entry。
    • 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。
    • 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
    • 其它类使用静态内部类需要使用“外部类.静态内部类”方式。

    5.1.4.2. 成员内部类

    • 成员内部类不能定义静态方法和变量(final 修饰的除外)。
    • 成员内部类可以王文外部类所有的变量和方法,包括private和static的。

    5.1.4.3. 局部内部类(定义在方法中的类)

    5.1.4.4. 匿名内部类(要继承一个父类或者实现一个接口、直接使用 new 来生成一个对象的引用)

    5.1.5. JAVA 泛型

    泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

    Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。

    类型通配符一般是使用?代替具体的类型参数,?泛型对象是只读的。

    • <? extends T>表示该通配符所代表的类型是 T 类型的子类。
    • <? super T>表示该通配符所代表的类型是 T 类型的父类。

    5.1.6. JAVA 序列化(创建可复用的 Java 对象)

    • 保存(持久化)对象及其状态到磁盘,或者远程方法调用时,需要序列化。
    • 序列化对象以字节数组保存,类的静态成员不保存。
    • 只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
    • 在类中增加 writeObject 和 readObject 方法可以实现自定义序列化策略。
    • 通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。
    • Transient 关键字阻止该变量被序列化到文件中(反序列化时会设置为初始值)。
    • 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)

    5.1.7. JAVA 对象的复制

    5.1.7.1. 直接赋值复制
    A a1 = a2,这实际上复制的是引用,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。
    同理,对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
    String类的特殊性在于其操作符重载时new了新对象,所以,有类似于基本类型一样的传值效果:String str = "Hello";等价于String str = new String("Hello");str = str + " world!";等价于str = new String((new StringBuffer(str)).append(" world!"));

    5.1.7.2. 浅复制(复制引用但不复制引用的对象)
    如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。

    5.1.7.3. 深复制(复制对象和其应用对象)
    深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
    可以利用序列化/反序列化机制来深复制一个对象:先使对象实现 Serializable 接口,然后把这个对象写到一个流里,再从流里读出来,便可以重建对象。

    6. SPRING

    6.1.1. Spring 特点

    Spring 是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层。但是 Spring 仍然可以和其他的框架无缝整合。
    Spring 具有轻量级,控制反转,面向切面,是容器和框架集合的特点。

    2014 年至 2017 年期间发布了许多 Spring 框架 4.xx 系列版本,包含了对Java 8 的全面支持;Spring 5.0 GA版本于2017年9月28日发布,不再支持JDK8以下的版本。

    6.1.2. Spring 核心组件

    6.1.3. Spring 常用模块

    1. 核心容器
      核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
    2. Spring 上下文
      Spring 上下文是一个配置文件,向Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
    3. Spring AOP
      通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便捷性。
    4. Spring DAO
      为JDBC DAO 抽象层提供了有意义的异常层次结构,并遵从通用的DAO 异常层次结构。
    5. Spring ORM
      Spring 框架插入了若干个ORM 框架,从而提供了ORM 的对象关系工具,其中包括JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和DAO 异常层次结构。
    6. Spring Web 模块
      Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与Jakarta Struts 的集成。
    7. Spring MVC 框架
      MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。MVC 框架是高度可配置的,并容纳了大量视图技术,其中包括 JSP、Velocity 和POI。

    6.1.4. Spring 主要包

    • org.springframework.core
      Spring 的核心工具包,其他包依赖此包。
    • org.springframework.beans
      所有应用都用到,包含访问配置文件,创建和管理 bean 等。
    • org.springframework.aop
      Spring 的面向切面编程,提供AOP (面向切面编程)的实现。
      -org.springframework.web.context
      提供在基础 IOC 功能上的扩展服务,此外还提供许多企业级服务的支持,有右键服务、任务调度、JNDI 定位、EJB 集成、远程访问、缓存以及多种视图层框架的支持。
    • org.springframework.web.mvc
      包含SpringMVC 应用开发时所需要的核心类。
    • org.springframework.transaction
      为JDBC、Hibernate、JDO、JPA 提供一致的声明式和编程式事务管理
    • org.springframework.web
      包含 Web 应用开发时,用到Spring 框架时所需的核心类。
    • org.springframework.aspects
      Spring 提供的对AspectJ 框架的整合。
    • org.springframework.test
      对 JUNIT 等测试框架的简单封装。
    • org.springframework.asm
      spring3.0 开始提供自己独立的 asm.jar 包。
    • org.springframework.context.support
      Spring context 的扩展支持,用于 MVC 方面。
    • org.springframework.expression
      Spring 表达式语言。
    • org.springframework.instrument.tomcat
      Spring 对 tomcat 连接池的集成。
    • org.springframework.instrument
      Spring 对服务器的代理接口。
    • org.springframework.jdbc
      对 JDBC 的简单封装。
    • org.springframework.jms
      为简化 jms api 的使用而做的简单封装。
    • org.springframework.orm
      整合第三方的 orm 实现,如 hibernate、ibatis、jdo、jpa等。
    • org.springframework.oxm
      Spring 对于 object/xml 映射的支持,可以让 JAVA 与XML 来回切换。
    • org.springframework.web.portlet
      Spring MVC 的增强。
    • org.springframework.web.servlet
      对 J2EE6.0 servlet3.0 的支持。
    • org.springframework.web.struts
      整合对 Struts 框架的支持,更方便更容易的集成Struts 框架。

    6.1.5. Spring 常用注解

    Spring的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用Spring注解方式,就和XML配置方式一样。

    • 声明bean的注解
      • @Component 组件,没有明确的角色
      • @Service 在业务逻辑层使用(service层)
      • @Repository 在数据访问层使用(dao层)
      • @Controller 在展现层使用,控制器的声明(C)
    • 注入bean的注解
      • @Autowired:由Spring提供(默认按照类型装配)
      • @Resource:由JSR-250提供(使用性更为灵活,可指定名称,也可以指定类型)
    • java配置类相关注解
      • @Configuration 声明当前类为配置类,作为 bean 定义的资源,相当于xml形式的Spring配置(类上)
      • @Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式;并可以使用@Scope注解等来详述该bean的作用域范围等(方法上)
      • @ComponentScan 用于对Component进行扫描,相当于xml中的(类上)
    • SpringMVC部分
      • @RequestMapping 用于映射Web请求,包括访问路径url和参数(类或方法上)
      • @ResponseBody 支持将返回值放在response内,而不是一个页面,通常用于返回json数据(返回值旁或方法上)
      • @RequestBody 使得方法参数被绑定到HTTP请求Body上。(放在参数前)
      • @PathVariable 将修饰的参数变为可供使用的uri变量,可用于动态绑定,支持Restful接口。例如,@RequestMapping(“/hello/{name}”)可以定义参数@PathVariable String name来提取。(放在参数前)
      • @RestController 该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,用于支持REST的API。
      • @ModelAttribute 当它作用在方法上时,标明该方法的目的是添加一个或多个模型属性(model attributes),并且该方法会在@RequestMapping修饰的方法调用之前被调用。
    • 其他
      • @Transactional 处理dao层或service层的事务操作时使用该注解,譬如删除失败时的回滚操作。(方法上)

    6.1.6. Spring 第三方结合

    持久层:

    • Hibernate:一个标准的ORM框架(对象关系映射),即完成数据库表和持久化类之间的映射
    • Mybatis:仅支持基本的字段映射;针对的SQL-Maping,专注sql本身,需要程序员自己编写sql语句

    6.1.7. Spring IOC 原理

    6.1.7.1. 概念
    Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供 了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。

    6.1.7.3. IOC 容器实现

    • BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;
    • ApplicationContext 派生自 BeanFactory, 面向开发者,提供了资源加载,消息源处理,事件机制,生命周期管理等必要功能;
    • WebApplicationContext是实现ApplicationContext接口的子类,是专门为WEB应用准备的。

    6.1.7.4. Spring Bean 作用域

    • singleton:单例模式,缺省作用域,多线程下不安全
    • prototype:原型模式,每次使用时创建,每个 Bean 实例都有自己的属性和状态
    • Request:一次 request 一个实例
    • session:一次 Http Session 一个实例
    • global Session:一个全局的 Http Session 一个实例,仅在 使用 portlet context 时有效

    6.1.7.5. Spring Bean 生命周期

    1. 实例化(new操作)
    2. IOC 依赖注入(配置,设置属性)
    3. 可选:setBeanName 实现
    4. 可选:BeanFactoryAware 实现
    5. 可选:ApplicationContextAware 实现
    6. 可选:postProcessBeforeInitialization 接口实现-初始化预处理
    7. 可选:init-method 自定义初始化
    8. 可选:postProcessAfterInitialization
    9. 可选:Destroy 过期自动清理阶段
    10. 可选:destroy-method 自定义清理

    6.1.7.6. Spring 依赖注入四种方式

    • 构造器注入
    • setter 方法注入
    • 静态工厂注入
    • 实例工厂

    6.1.7.7. 5 种不同方式的自动装配
    Spring 装配包括手动装配和自动装配,手动装配是有基于 xml 装配、构造方法、setter 方法等,自动装配方式来进行依赖注入的方法如下:

    • no:默认的方式是不进行自动装配
    • byName:通过参数名 自动装配,容器试图匹配、装配和该 bean 的属性具有相同名字的 bean
    • byType:通过参数类型自动装配,容器试图匹配、装配和该 bean 的属性具有相同类型的 bean
    • constructor:这个方式类似于 byType, 但是要提供给构造器参数
    • autodetect:首先尝试使用 constructor 来自动装配,如果无法工作,则使用 byType 方式

    5.1.8. Spring AOP 原理

    业务处理的主要流 程是核心关注点,与之关系不大的部分是横切关注点(比如,比如权限认证、日志、事务)。
    AOP 的作用在于将核心关注点和横切关注点分离开来。

    所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来的可重用模块。
    切面便于减少系统的重复代码,降低模块之间的耦合度,并有利于未 来的可操作性和可维护性。

    Spring 提供了两种方式来生成代理对象: JDKProxy 和 CGLib,默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 CGLib 来生成代理。

    AOP实例:

    @Aspect
    public class TransactionDemo {
        // 基于注解的方式:@Pointcut("@annotation(com.annotations.MyAnnotation)")
        @Pointcut(value = "execution(* com.core.service.*.*.*(..))")
        public void point() {
        }
    
        @Before(value = "point()")
        public void before() {
            System.out.println("transaction begin");
        }
    
        @AfterReturning(value = "point()")
        public void after() {
            System.out.println("transaction commit");
        }
    
        @Around("point()")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("transaction begin");
            joinPoint.proceed();
            System.out.println("transaction commit");
        }
    }
    

    5.1.9. Spring MVC原理

    Spring 的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,流程如下:

    1. Http 请求到 DispatcherServlet
    2. DispatcherServlet 查询一个或多个 HandlerMapping 寻找处理器
    3. DispatcherServlet 调用处理器 Controller
    4. Controller 调用业务逻辑处理后,给DispatcherServlet返回 ModelAndView
    5. DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图,并将模型数据传给View显示
    6. View负责将结果显示到客户端,完成Http响应

    5.1.10. Spring Boot 原理

    Spring Boot只是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,使开发、测试和部署更加快捷、方便和高效。

    5.1.11. JPA 原理(Java Persistence API)

    6.1.11.2. 本地事务
    紧密依赖于底层资源管理器(例如数据库连接 ),举例:

        public void transferAccount() {
            Connection conn = null;
            Statement stmt = null;
            try {
                conn = getDataSource().getConnection();
                // 将自动提交设置为 false,若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
                conn.setAutoCommit(false);
                stmt = conn.createStatement();
                stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'");
                stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'");
                // 提交事务 
                conn.commit();
                // 至此,事务提交成功: 转账的两步操作同时成功 
            } catch (SQLException sqle) {
                // 发生异常,回滚在本事务中的操作
                conn.rollback();
                // 至此,事务回滚成功: 转账的两步操作完全撤销 
                stmt.close();
                conn.close();
            }
        }
    

    6.1.11.1. 分布式事务
    Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。
    分布式事务(Distributed Transaction)包括事务 管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。
    在实现上,使用两阶段提交(准备阶段和提交阶段)来保证分布式事务的原子性。

    在使用中,我们可以将资源管理器看做任意类型的持久化数据存储。
    事务管理器承担着所有事务 参与单元的协调与控制,我们不必关心。
    实例如下:

        public void transferAccount() {
            UserTransaction userTx = null;
            Connection connA = null;
            Statement stmtA = null;
            Connection connB = null;
            Statement stmtB = null;
            try {
                // 获得 Transaction 管理对象
                userTx = (UserTransaction) getContext().lookup("java:comp/UserTransaction");
                connA = getDataSourceA().getConnection();
                connB = getDataSourceB().getConnection();
                // 启动事务
                userTx.begin();
                stmtA = connA.createStatement();
                stmtA.execute("update t_account set amount = amount - 500 where account_id = 'A'");
                stmtB = connB.createStatement();
                stmtB.execute("update t_account set amount = amount + 500 where account_id = 'B'");
                // 提交事务
                userTx.commit();
                // 至此,事务提交成功: 转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
            } catch (SQLException sqle) {
                // 发生异常,回滚在本事务中的操作
                userTx.rollback();
                // 至此,事务回滚成功: 数据库 A 和数据库 B 中的数据更新被同时撤销
            } catch (Exception ne) {
            }
        }
    

    5.1.12. Mybatis 缓存

    Mybatis 中,默认开启一级缓存且不能关闭。

    • 一级缓存 是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以 后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。出现 commit 操作(增删改),本 sqlsession 中的一级缓存区域全部清空。
    • 二级缓存 是不同的 sqlsession 可以共享的 mapper 级别的缓存。

    7. 微服务

    7.1.1. 服务注册发现

    当下用于服 务注册的工具非常多,如 ZooKeeper,还有Consul,Eureka,SmartStack,Etcd 等。

    7.1.1.1. 客户端注册(zookeeper)
    客户端注册是服务自身要负责注册与注销的工作。
    期间客户端还需要和注册中心保持心跳,心跳可以由客户端或者注册中心负责。

    7.1.1.2. 第三方注册(独立的服务 Registrar)
    第三方注册由一个独立的服务 Registrar 负责注册与注销。
    当服务启动后以某种方式通知 Registrar, 然后 Registrar 负责向注册中心发起注册工作。同时 Registrar 要维护与服务之间的心跳,当服务不 可用时,向注册中心注销服务。

    7.1.1.3. 客户端发现
    客户端发现是指客户端负责查询可用服务的实际地址并请求服务,以及负责负载均衡的工作。

    7.1.1.4. 服务端发现
    服务端发现需要额外的 Router 服务,请求先打到 Router,然后 Router 负责查询服务与负载均衡。

    7.1.2. API 网关

    API Gateway 负责请求转发、聚合结果和协议转换。
    所有来自客户端的请求都要先经过 API Gateway,然后路由这些请求到对应的一个或多个微服务,并聚合结果。
    还可能有 其他功能,如安全认证、监控、负载均衡、缓存、请求分片和管理、静态响应处理等。

    7.1.3. 配置中心

    配置中心一般用作系统的参数配置,它需要满足如下几个要求:高效获取、实时感知、分布式访
    问。
    基于 zookeeper 配置中心的实现架构中,采取数据加载到内存方式解决高效获取的问题,借助 zookeeper 的节点监听机制来实现实时感知。

    7.1.4. 事件调度(kafka)

    消息服务和事件的统一调度,常用用 kafka ,activemq 等。

    7.1.5. 服务跟踪(starter-sleuth)

    Spring Cloud Sleuth 通过在日志中引入唯一标识 Trace ID,将请求过程的所有日志关联起来,可以跟踪一个请求从一个微服务到下一个微服务的传播过程。

    7.1.6. 服务熔断(Hystrix)

    当 Hystrix Command 请求后端服务失败数量超过一定比例(默认 50%), 断路器会 切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务.
    断路器保持在开路状态 一段时间后(默认 5 秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN).

    服务熔断可以避免服务雪崩效应,避免发送大量无效 请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。

    7.1.7. API 管理

    Swagger,完成定义接口及接口相关信息的描述文件,就可以自动生成接口文档(多格式)和客户端、服务端的代码(多语言),以及在线接口调试页面等等。

    而有了Spring框架的支持,通过在项目中引入Springfox(前身为Spring-swagger项目),可以扫描相关的代码,反向生成描述文件,进而生成与代码一致的接口文档和客户端代码。

    8. NETTY 与 RPC

    8.1.1. Netty 原理

    Netty 是一个高性能、异步事件驱动的 NIO 框架,基于 JAVA NIO 提供的 API 实现。
    它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞 的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。

    8.1.2. Netty 高性能

    • Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端 Channel,由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 IO 阻塞导致的线程挂起。
    • Netty 采用了异步通信模式,一个 IO 线程可以并发处理 N 个客户端连接和读写操作。
    • Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写, 不需要进行字节缓冲区的二次拷贝。
    • Netty 提供了基于内存池的缓冲区重用机制,优化堆外直接内存的分配和回收。
    • 高效的 Reactor 线程模型,支持Reactor 多线程模型, 主从 Reactor 多线程模型。
    • Netty 采用了串行无锁化设计。只要用户不主动切换线程,一直会由 NioEventLoop 调用 到用户的 Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁 的竞争,从性能角度看是最优的。
    • Netty 默认提供了对 Google Protobuf 的支持,通过扩展 Netty 的编解码接口,用户可以实现其它的高性能序列化框架。
    • 将每个连接和 cpu 绑定,并通过连接的 hash 值,来均衡软中断在多个 cpu 上,提升 网络并行处理性能。

    8.1.3. Netty RPC实现

    8.1.3.2. 关键技术

    • 服务发布与订阅:服务端使用 Zookeeper 注册服务地址,客户端从 Zookeeper 获取可用的服务 地址。
    • 通信:使用 Netty 作为通信框架。
    • Spring:使用 Spring 配置服务,加载 Bean,扫描注解。
    • 动态代理:客户端使用代理模式透明化服务调用。
    • 消息编解码:使用 Protostuff 序列化和反序列化消息。

    8.1.3.4. 消息编解码
    request消息数据结构:接口名称+方法名+参数类型和参数值+超时时间+ requestID
    response消息数据结构:返回值+状态 code+requestID

    8.1.3.1. 通讯过程

    1. AtomicLong自增来生成requestID
    2. 以requestID为key来存放回调对象 callback 到全局 ConcurrentHashMap
    3. 执行 callback 的 get()方法试 图获取远程返回的结果:synchronized 获取回调对象 callback 的锁并自旋 wait
    4. 监听消息的线程收到消息,找到 callback 上的锁并唤醒

    8.1.4. RMI 实现方式

    Java RMI(Java Remote Method Invocation,Java 远程方法调用)是 Java 编程语言里,一种用 于实现RPC(Remote Procedure Call,远程过程调用)的应用程序编程接口。

    8.1.5. Protocol Buffer

    Protocol Buffer 是 google 的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法。具有语言无关,平台无关,体积小,序列化速度快,扩展性和兼容性好等优点。缺点是它以二进制流方式存储(不可读),从而自解释性差,通用性差。

    8.1.6. Thrift

    Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。
    它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中创建高效的、无缝的服务,其传输数据采用二进制格式, 对于高并发、大数据量和多语言的环境更有优势。

    9. 网络

    9.1.1. 网络 7 层架构

    1. 物理层: 主要定义物理设备标准,主要作用是传输比特流。
    2. 数据链路层: 主要将数据进行 MAC 地址(网卡的地址)的封装与解封装。数据叫做帧,工作的设备是交换机。
    3. 网络层: 主要将数据进行 IP 地址(例 192.168.0.1)的封装与解封装。数据叫做数据包,工作的设备是路由器。
    4. 传输层: 定义了一些传输数据的协议和端口号,如:TCP,UDP。 主要是将数据进行分段进行传输,到达目的地址后在进行重组。 常常把这一层数据叫做段。
    5. 会话层: 建立数据传输的通路,发起会话或或者接受会话请求。
    6. 表示层: 主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等。
    7. 应用层: 主要是一些终端的应用软件。

    9.1.2. TCP/IP 原理

    TCP/IP 协议不是 TCP 和 IP 这两个协议的合称,而是指因特网整个 TCP/IP 协议族:

    1. 网络接口层:对应物理层+数据链路层
    2. 网络层:IP协议
    3. 传输层:TCP、UDP
    4. 应用层:HTTP、FTP、DNS、TELNET、SMTP等

    9.1.3. TCP 三次握手/四次挥手

    9.1.3.1. 数据包说明

    1. 源端口号( 16 位)
    2. 目的端口号( 16 位)
    3. 顺序号 seq( 32 位): 表示TCP中数据字节的顺序号。初始顺序号不一定为0,顺序号递增到最大值再次从0开始。
    4. 确认号 ack( 32 位): 仅当 ACK 标志为 1 时有效,表示所期望收到的下一个顺序号(上次已成功收到数据字节顺序号加 1)。
    5. TCP 报头长度( 4 位): 报头中 32bit 字的数目,它实际上指明数据从哪里开始(固定报头20字节,可选项最长40字节)。
    6. 保留位( 6 位): 保留给将来使用,目前必须置为 0 。
    7. 控制位( control flags , 6 位): 6 个标志比特,多个可同时被设置为 1 。
      • URG :为 1 表示紧急指针有效,为 0 则忽略紧急指针值。
      • ACK :为 1 表示确认号有效,为 0 表示报文中不包含确认信息,忽略确认号字段。
      • PSH :为 1 表示是带有 PUSH 标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
      • RST :用于复位由于主机崩溃或其他原因而出现错误的连接。它还可以用于拒绝非法的报文段和拒绝连接请求。一般情况下,如果收到一个 RST 为 1 的报文,那么一定发生了某些问题。
      • SYN :同步序号,为 1 表示连接请求,用于建立连接和使顺序号同步( synchronize )。
      • FIN :用于释放连接,为 1 表示发送方已经没有数据发送了,即关闭本方数据流。
    8. 窗口大小( 16 位): 源方可以接收的字节数,即源方接收窗口大小。
    9. 校验和( 16 位): 对头部和数据的整个 TCP 报文段的校验和,强制性字段,发送端计算和存储,并由接收端进行验证。
    10. 紧急指针(16位): 仅当 URG 标志置1时有效,和seq一起运算来指示紧急数据的字节号。例如,Telnet会将中止字符作为紧急数据发送到远程端。(紧急数据已经是废弃的功能)
    11. 可选项: 最常见的可选字段是最长报文大小,又称为MSS(MaximumSegmentSize)。每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN 标志的那个段)中指明这个选项, 它指明本端所能接收的最大长度的报文段。可选项长度不一定是 32 位字的整数倍,所以要加填充位,使得报头长度成为整字数。
    12. 数据: 数据部分是可选的,例如:连接建立和终止;没有数据发送,只是确认收到数据;处理超时等。

    9.1.3.2. 三次握手

    1. 客户端发送SYN包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认。
    2. 服务器收到SYN包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
    3. 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

    9.1.3.3. 四次挥手

    1. 客户端发送FIN包(seq=u),用来关闭客户端到服务器的数据传送,并进入FIN_WAIT_1状态, 等待服务器确认。
    2. 服务器收到FIN包,发回一个 ACK包(ack=u+1),此包发送完毕,客户端进入FIN_WAIT_2状态,服务器进入CLOSE_WAIT状态。此时,如果服务器还有未发送完的数据,可以继续发送给客户端。
    3. 服务器没有数据需要发送了。发送FIN包(seq=v),用来关闭服务器到客户端的数据传送,并进入LAST_ACK状态。
    4. 客户端收到FIN包,发回一个 ACK包(ack=v+1),此包发送完毕,客户端进入TIME_WAIT状态,服务器进入CLOSED状态。客户端再等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后,进入CLOSED状态。

    9.1.4. HTTP 原理

    HTTP 是一个无状态的协议。无状态是指客户端和服务器之间不需要建立持久的连接,客户端发送请求,服务器返回响应,然后连接就被关闭了。

    9.1.4.1. 传输流程

    1. 地址解析
    2. 封装 HTTP 请求数据包
    3. 封装成 TCP 包并建立连接
    4. 客户端发送请求命令,请求信息组成:请求方法URI协议/版本,请求头,请求正文
    5. 服务器响应,响应信息组成:状态行,响应头,响应正文
    6. 服务器关闭 TCP 连接。如果设置了Connection:keep-alive,则不会关闭TCP连接,从而节省了为每个请求建立新连接所需的时间。

    请求和响应信息示例:

    curl -v -d '{"jsonData":"test"}' "http://www.baidu.com/index.html?param=test"
    
    *   Trying 220.181.38.150...
    * TCP_NODELAY set
    * Connected to www.baidu.com (220.181.38.150) port 80 (#0)
    > POST /index.html?param=test HTTP/1.1   ===== 请求方法URI协议/版本
    > Host: www.baidu.com   ===== 请求头,从此处开始的若干行
    > User-Agent: curl/7.64.1
    > Accept: */*
    > Content-Length: 19
    > Content-Type: application/x-www-form-urlencoded
    >
    * upload completely sent off: 19 out of 19 bytes   ===== 请求正文
    < HTTP/1.1 200 OK   ===== 状态行
    < Accept-Ranges: bytes   ===== 响应头,从此处开始的若干行
    < Cache-Control: max-age=1
    < Connection: Keep-Alive
    < Content-Length: 7382
    < Content-Type: text/html
    < Date: Wed, 29 Jan 2020 13:57:28 GMT
    < Etag: "1cd6-5480030886bc0"
    < Expires: Wed, 29 Jan 2020 13:57:29 GMT
    < Last-Modified: Wed, 08 Feb 2017 07:55:35 GMT
    < P3p: CP=" OTI DSP COR IVA OUR IND COM "
    < Server: Apache
    < Set-Cookie: BAIDUID=4C832C030A4D45EE53C111F5FEE4B72C:FG=1; expires=Thu, 28-Jan-21 13:57:28 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
    < Vary: Accept-Encoding,User-Agent
    <
    <!doctype html><html>从略...   ===== 响应正文
    * Connection #0 to host www.baidu.com left intact
    * Closing connection 0
    

    9.1.4.2. HTTP 状态

    • 1xx:消息响应
      • 100 继续
    • 2xx:成功响应
      • 200 成功
    • 3xx:重定向
      • 301 永久移动
      • 302 临时移动
      • 304 未修改(使用浏览器缓存数据)
    • 4xx:客户端错误
      • 400 请求无效(Bad Request,如格式错误的请求语法)
      • 401 未授权(Unauthorized,缺失或者错误的认证)
      • 403 禁止访问(Forbidden,服务器完成认证过程,但是客户端请求没有权限去访问要求的资源)
      • 404 未找到资源
    • 5xx:服务器错误
      • 500 内部服务器错误
      • 502 网关错误(Bad Gateway,作为网关或者代理工作的服务器如Nginx尝试执行请求时,从上游服务器接收到无效的响应。一般是上游服务器挂了。)
      • 503 服务不可用(Service Unavailable,由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。)
      • 504 网关超时(Gateway Timeout,作为网关或者代理工作的服务器如Nginx尝试执行请求时,未能及时从上游服务器收到响应。)

    9.1.4.3. HTTPS
    HTTPS即 HTTP 下加入 SSL 层,其端口号是443。

    1. 建立连接获取证书:客户端发送支持的加密算法的请求,服务器端回应本次通信使用的算法和证书。
    2. 证书验证:判断证书中的域名和当前请求域名一致,判断签发机构在可信列表,使用签发机构公钥来验证签名有效。
    3. 数据加密和传输:生成对称秘钥并使用证书里服务器的公钥进行加密后发送给服务器,服务器使用它的私钥解密后获得对称密钥,并在客户端和服务器之间开始进行对称加密的通信。

    9.1.5. CDN 原理

    CDN一般包含分发服务系统、负载均衡系统和管理系统:

    • 分发服务系统基本的工作单元就是各个 Cache 服务器,负责直接响应用户请求,将内容快速分发到用户;同时还 负责内容更新,保证和源站内容的同步。
    • 负载均衡系统负责对所有的用户请求进行调度,确定提供给用户的最终访问 地址。
    • 管理系统实现对 CDN 系统的设备管理、拓扑管理、链路监控和故障管理等,以及CDN 系统的业务管理等。

    10. 日志

    10.1.1. Slf4j

    slf4j 的全称是 Simple Loging Facade For Java,即它仅仅是一个为 Java 程序提供日志输出的统一接口,并不是一个具体的日志实现方案。
    slf4j 在底层整合了众多日志框架(logback, log4j等),可以方便的切换底层的日志框架,而不用修改代码。

    10.1.2. Log4j

    Log4j 是 Apache 的一个开源项目,是目前最流行的日志框架之一。
    它使用Logger来控制启用哪些日志并设置级别,使用Appenders指定输出端,使用Layout控制日志格式。

    10.1.3. LogBack

    Logback 被认为是 Log4J 的继承人。
    Logback原生实现了 SLF4J API(Log4J 还需要有一个中间转换层),并且速度更快,测试更充分,文档更齐全。
    还提供了配置文件自动热加载,透明和快速的从I/O错误中恢复,自动压缩字和删除旧日志等更多功能。

    10.1.4. ELK

    ELK 是软件集合 Elasticsearch、Logstash、Kibana 的简称,由这三个软件及其相关的组件可以打造大规模日志实时处理系统。

    • Elasticsearch 是一个基于 Lucene 的、支持全文索引的分布式存储和索引引擎,主要负责将 日志索引并存储起来,方便业务方检索查询。
    • Logstash 是一个日志收集、过滤、转发的中间件,主要负责将各条业务线的各类日志统一收集、过滤后,转发给 Elasticsearch 进行下一步处理。
    • Kibana 是一个可视化工具,主要负责查询 Elasticsearch 的数据并以可视化的方式展现给业务方,比如各类饼图、直方图、区域图等。

    11. ZOOKEEPER

    11.1.1. Zookeeper 概念和角色

    Zookeeper 是一个分布式协调服务,可用于服务发现,配置管理等。

    一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器进行同步,只要有超过半数节点写入成功,该写请求就会被提交。
    一个 Zookeeper 集群可能同时存在多个 Follower,负责直接处理并返回客户端的读请求,写请求需要转发给 Leader 处理,并在 Leader 处理写请求时对请求进行投票。
    Observer 与 Follower 类似,但是无投票权。加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。

    11.1.2. Zookeeper 工作原理(原子广播)

    Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。

    Zab 协议有两种模式,它们分别是恢复模式和广播模式。
    当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了,就进入了广播模式。

    当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,无法提供正常服务,直到恢复模式结束,此过程大约需要30s或者更多。

    12. KAFKA

    12.1.1. Kafka 概念

    Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统。

    12.1.2. Kafka 数据存储设计

    partition的数据文件包含属性:offset,MessageSize,data。
    数据文件分段segment存储,顺序读写,二分查找定位Message。
    数据文件索引是分段索引,稀疏存储保存在内存中。

    12.1.3. 生产者设计

    负载均衡:partition会均衡分布到不同broker(Kafka服务器)上。
    一次请求批量发送消息。
    支持压缩消息(GZIP或Snappy)。

    12.1.4. 消费者设计

    同一 Consumer Group 中的多个 Consumer 实例,不同时消费同一个 partition。

    13. RabbitMQ

    13.1.1. 概念

    RabbitMQ 是一个由 Erlang 语言开发的 AMQP(Advanced Message Queue,高级消息队列协议) 的开源实现。

    13.1.3. Exchange 类型

    Exchange 交换器,负责将消息路由给队列,然后消费者从队列中获取消息。
    目前有4种Exchange类型:

    • Direct: 消息的路由键(routing key)如果和 Binding(基于路由键将交换器和消息队列连接起来的路由规则)中的路由键一致,就将消息发到对应的队列中。它是完全匹配、单播的模式。
    • Fanout: 消息会分到该交换器所有绑定的队列上去。fanout 类型转发消息是最快的。
    • topic 交换器: 队列需要绑定到一个模式上(支持通配符),如果消息的路由键和该模式相匹配,就将消息发送给对应的队列。
    • headers: 类似Exchange,但是是匹配消息的 header,性能差很多,目前几乎用不到。

    14. HBASE

    14.1.1. 概念

    Hbase 是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。
    HDFS 为 Hbase 提供可靠的底层数据存储服务,MapReduce 为 Hbase 提供高性能的计算能力,Zookeeper 为 Hbase 提供稳定服务和 Failover 机制。

    14.1.2. 列式存储

    Hbase 是根据列族来存储数据的。

    14.1.3. Hbase 核心概念

    • Column Family 列族,列族下面可以包含任意多的列。官方推荐的是列族最好小于或者等于 3。
    • Hbase 使用 Rowkey 来唯一的区分某一行的数据。
    • Hbase 会将一个大表的数据基于 Rowkey 的不同范围分配到不同的 Region 分区中。
    • 在 Hbase 中使用不同的 TimeStamp 来标识相同 rowkey 行对应的不同版本的数据。

    14.1.5. Hbase 的写逻辑

    1. Client 获取数据写入的 Region 所在的 RegionServer。(通过zookeeper -> META -> 数据)
    2. 请求写 Hlog。(Hlog 存储在 HDFS,当 RegionServer 出现异常,需要使用 Hlog 来恢复数据)
    3. 请求写 MemStore。(MemStore 后续会逐渐刷到 HDFS 中)

    15. MongoDB

    MongoDB 是一个基于分布式的面向文档存储的开源数据库系统。

    16. CASSANDRA

    16.1.1. 概念

    Apache Cassandra 是高度可扩展的,高性能的分布式 NoSQL 数据库。

    16.1.4. Gossip 协议

    在一个有界网络中, 每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。
    因为 Gossip 不要求节点知道所有其他节点,因此又具有去中心化的特点,节点之间完全对等。
    Gossip是弱一致性,是“最终一致性”,适合的领域有: 失败检测、 路由同步、Pub/Sub、动态负载均衡。

    16.1.6. 数据写请求和协调者

    协调者(coordinator)将 write 请求发送到拥有对应 row 的所有 replica 节点,只要节点可用便获取并执行写请求。
    写一致性级别(write consistency level)确定要有多少个 replica 节点必须返回成功的确认信息。

    16.1.7. 数据读请求和后台修复

    协调者首先与一致性级别确定的所有 replica 联系,获得请求的数据,然后使用含 最新数据的 replica 向 client 返回结果。
    协调者在后台联系和比较来自其余拥有对应 row 的 replica 的数据,若不一致,会向过时的 replica 发写请求用最新的数据进行更新 read repair。

    16.1.8. 数据存储(CommitLog、MemTable、SSTable)

    写请求分别到磁盘的 CommitLog 和内存的 MemTable, 并且 MemTable 的数据会不定时刷写到磁盘 SSTable 上。

    16.1.10. 数据读写

    Cassandra 中,无论是 insert 还是 remove 操作,都是在已有的数据后面进行追加,而不修改已有的数据。
    这种设计称为 Log structured 存储,就是系统中的数据是以日志的形式存在的。
    这种设计使得数据的写和删除效率极高,错误恢复简单;但是读的复杂度很高。

    删除一个 column 其实只是插入一个关于这个 column 的墓碑(tombstone),并不直接删除原 有的 column。

    cassandra 读取的数据是 memtable 中的数据和 SStables 中数据的合并结果。

    17. 设计模式

    17.1.1. 设计原则

    1. 开闭原则:对扩展开放,对修改关闭
    2. 里氏替换原则:继承必须确保父类所拥有的性质在子类中仍然成立
    3. 依赖倒置原则:要面向接口编程,不要面向实现编程
    4. 单一职责原则:对象不应该承担太多职责
    5. 接口隔离原则:拆分接口,只包含客户感兴趣的方法
    6. 最少知识原则:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用
    7. 合成复用原则:在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

    17.1.2. 工厂方法模式

    定义一个用于创建产品的接口,由子类决定生产什么产品。

    17.1.3. 抽象工厂模式

    提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

    17.1.4. 单例模式

    某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

    17.1.5. 建造者模式

    将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

    17.1.6. 原型模式

    将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

    17.1.7. 适配器模式

    将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

    17.1.8. 装饰器模式

    动态的给对象增加一些职责,即增加其额外的功能。

    17.1.9. 代理模式

    为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

    17.1.10. 外观模式

    为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

    17.1.11. 桥接模式

    将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

    17.1.12. 组合模式

    将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

    17.1.13. 享元模式

    运用共享技术来有效地支持大量细粒度对象的复用。

    17.1.14. 策略模式

    定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。

    17.1.15. 模板方法模式

    定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

    17.1.16. 观察者模式

    多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。

    17.1.17. 迭代器模式

    提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

    17.1.18. 责任链模式

    把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。

    17.1.19. 命令模式

    将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

    17.1.20. 备忘录模式

    在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

    17.1.21. 状态模式

    允许一个对象在其内部状态发生改变时改变其行为能力。

    17.1.22. 访问者模式

    在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。

    17.1.23. 中介者模式

    定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。

    17.1.24. 解释器模式

    提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

    18. 负载均衡

    18.1.1. 四层负载均衡vs七层负载均衡

    18.1.1.1. 四层负载均衡(目标地址和端口交换)
    例如,nginx,F5,lvs,haproxy

    18.1.1.2. 七层负载均衡(内容交换)
    例如,haproxy,nginx,apache,Mysql proxy

    18.1.2. 负载均衡算法/策略

    18.1.2.1. 轮循均衡(RoundRobin)
    18.1.2.2. 权重轮循均衡(WeightedRoundRobin)
    18.1.2.3. 随机均衡(Random)
    18.1.2.4. 权重随机均衡(WeightedRandom)
    18.1.2.5. 响应速度均衡(ResponseTime探测时间)
    18.1.2.6. 最少连接数均衡(LeastConnection)
    18.1.2.7. 处理能力均衡(CPU、内存等换算)
    18.1.2.8. DNS响应均衡(FlashDNS)
    18.1.2.9. 哈希算法
    18.1.2.10. IP 地址散列(保证客户端服务器对应关系稳定)
    18.1.2.11.URL 散列

    18.1.3. LVS

    LVS(Linux Virtual Server) 的 IP 负载均衡技术是通过 IPVS 模块来实现的,IPVS虚拟出一个 IP 地址 VIP,即 Virtual IP,访问的请求首先经过 VIP 到达负载调度器,然后由负载调度器从 Real Server 列表中选取一个服务节点响应用户的请求。

    调度器转发请求到后端Real Server的模式有以下几种:

    • LVS NAT 模式:改写目标IP为RIP(Real Server IP),源IP不变
    • LVS DR 模式:改写源MAC地址和目标MAC地址,源和目标IP不变
    • LVS TUN 模式:再封装一层 IP 报文,源IP为DIP(Director Server IP,IVS所在服务器),目标IP为RIP
    • LVSFULLNAT模式:改写目标IP为RIP,源IP为DIP

    18.1.4. Keepalive

    Keepalive加入了 vrrp(virtual router redundancy protocal, 虚拟路由器冗余协议) 的功 能,因此,一方面具有 LVS cluster node healthcheck 功能,另一方面也具有 LVS director failover。

    18.1.5. Nginx 反向代理负载均衡

    LVS 实现的功能只是对请求数据包的转发、传递,Real Server 看到的请求还是来自客户端的真实用户;反向代理服务器在接收访问用户请求后,会代理用户重新发起请求代理下的 Real Server, 最后把数据返回给客户端用户,Real Server 看到的请求是来自代理服务器。
    Nginx 采用的是多进程(单线程) & 多路IO复用模型,会有一个 master 进程,它fork出来多个相互独立的 worker 子进程,worker 进程数,一般会设置成机器 cpu 核数。

    18.1.6. HAProxy

    HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。
    HAProxy实现了一种非阻塞,事件驱动, 单一进程模型,此模型支持非常大的并发连接数。支持多进程,但官方不推荐,因为每个进程有自己的内存区域会造成一些问题。

    19. 数据库

    19.1.1. 存储引擎

    数据库存储引擎是数据库底层软件组织,不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能:

    • InnoDB:底层存储结构为 B+树,只有它支持外键约束,支持事务,对频繁更新友好。从MySQL5.1开始是默认引擎。
    • TokuDB:底层存储结构为 Fractal Tree,类似B+树,非叶子节点带Message Buffer来缓存更新数据。支持事务,写入速度快,主要适合归档数据。
    • MyIASM:不支持事务,行级锁和外键,但是读取速度且占用内存和存储空间少。
    • Memory:使用存在内存中的内容来创建表,同时支持散列索引(默认)和 B 树索引。

    19.1.2. 索引

    • 从业务角度选择:
      • 为经常需要排序、分组和联合操作的字段建立索引。
      • 为常作为查询条件的字段建立索引。
    • 从实现原理角度选择:
      • 范围查询,函数都不走索引。(覆盖查询下不等于会走索引,覆盖索引:SQL只需要通过索引就可以返回查询所需要的数据)
      • 联合索引是最左前缀匹配原则:区分度大(字段不重复的比例)的字段放在前面,避免重复索引。
      • 尽量选择区分度高的列作为索引。
      • 如果索引字段的值很长,建立前缀索引,或添加CRC32或MD5伪列并建立索引。
      • 不使用外键,而是在应用程序中处理该约束逻辑。原因是外键会带来性能,并发,分库分表扩展性等多方面问题。
    • 索引维护:
      • 限制索引的数目,删除不再使用或很少使用的索引: 越多的索引,会使更新表变得很浪费时间。
      • 尽量的扩展索引,不要新建索引。

    19.1.3. 数据库三范式

    第一范式(1st NF -列都是不可再分):确保每列的原子性
    第二范式(2nd NF-非主键列不存在对主键的部分依赖):每个表只描述一件事情
    第三范式(3rd NF- 不存在对非主键列的传递依赖)

    19.1.4. 数据库是事务

    事务(TRANSACTION)是一个不可分割的工作逻辑单元,具备ACID属性:原子性、一致性、隔离性、永久性。

    19.1.5. 存储过程(特定功能的SQL语句集)

    存储过程经过第一次编译后再次调用不需要再次编译。注意点如下:

    1. 使用平均函数等来替代一些小循环。
    2. 中间结果存放于临时表,并加索引。
    3. 少使用游标,它是过程运算,效率低。
    4. 事务越短越好,否则会造成并发操作的阻塞,死锁。
    5. 使用 try-catch 处理错误异常。

    19.1.6. 触发器(一段能自动执行的程序)

    触发器是当对某一个表进行操作时触发,是一种特殊的存储过程。

    19.1.7. 数据库并发策略

    19.1.7.1. 乐观锁
    乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。

    19.1.7.2. 悲观锁
    悲观锁开始读取以及改变该对象之前就将对象锁住,并且直到提交了所作的更改之后才释放锁。
    悲观锁所说的加“锁”,分别是:排它锁(写锁)和共享锁(读锁)。

    19.1.7.3. 时间戳
    时间戳不使用锁机制,它在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加 1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存。

    19.1.8. 数据库锁

    又分为:行级锁、表级锁、页级锁

    19.1.9. 基于Redis分布式锁

    19.1.10. 分区分表

    19.1.11. 两阶段提交协议

    两阶段提交协议是分布式事务的一种算法,参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作,分为准备阶段和提交阶段。

    19.1.12. 三阶段提交协议

    与两阶段提交不同的是:

    1. 引入超时机制。同时在协调者和参与者中都引入超时机制。
    2. 在第一阶段和第二阶段中插入一个准备阶段,分CanCommit、PreCommit、DoCommit 三个阶段。保证了在最后提交阶段之前各参与节点的状态是 一致的。

    19.1.13. 柔性事务

    柔性事务遵循BASE理论,包括 基本可用(Basically Available)、柔性状态(Soft State)、最终一致性 (Eventual Consistency)。分为以下几种:

    • 两阶段型:就是分布式事务两阶段提交。
    • 补偿型:TCC 型事务(Try/Confirm/Cancel)可以归为补偿型。
    • 异步确保型:将一系列同步的事务操作变为基于消息执行的异步操作。
    • 最大努力通知型(多次尝试):异步消息通知,并允许在达到最大重试次数之后正常 结束事务。

    19.1.14. CAP

    CAP 原则指的是在一个分布式系统中, Consistency(一致性)、 Availability (可用性)、Partition tolerance(分区容错性),三者不可兼得。

    20. 一致性算法

    在分布式系统中,为保证每个节点执行相同的命令序列,需要在每一条指令上执 行一个“一致性算法”以保证每个节点看到的指令一致。

    20.1.1. Paxos Paxos

    Paxos 算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。其过程分为两个阶段:

    • 准 leader 确定:Proposer 向半数以上的 Acceptor 发送编号为 N 的 Prepare 请求;当且仅当 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号时,它才会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给 Proposer。
    • leader 确认:如果 Proposer 收到半数以上 Acceptor 的响应,它就会发送一个针对[N,V]提案的 Accept 请求给半数以上的 Acceptor,其中 V 是收到的响应中编号最大的提案的 value。当且仅当 N 大于或等于该 Acceptor 已经响应过的所有 Prepare 请求的编号时,它才会接受该提案。

    20.1.2. Zab

    Zab( ZooKeeper Atomic Broadcast , ZooKeeper 原子消息广播协议)协议,会经历三个步骤达到消息广播状态:

    • 初始化/崩溃恢复: 主要就是 Leader 选举过程;
    • 数据同步: Leader 服务器与其他服务器进行数据同步;
    • 消息广播: Leader 服务器将数据发送给其他服务器。

    20.1.3. Raft

    Raft 和 Paxos 一样只要保证 n/2+1 节 点正常就能够提供服务。
    Raft 的选举由定时器来触发,每个节点中定时器的时间都是随机的。Safety 就是用于保证选举出来的 Leader 一定包含先前 commited Log 的机制。当请求投票的该 Candidate 的 Term 较大 或 Term 相同 Index 更大则投票,从而,拥有最新的log的follower会成为新的leader。

    20.1.4. NWR

    N: 在分布式存储系统中,有多少份备份数据
    W: 代表一次成功的更新操作要求至少有 w 份数据写入成功
    R: 代表一次成功的读数据操作要求至少有 R 份数据成功读取

    NWR 值的不同组合会产生不同的一致性效果。根据鸽巢原理,当且仅当 W+R>N 的时候,整个系统对于客户端来讲才能保证强一致性。

    20.1.5. Gossip

    Gossip 算法又被称为反熵(Anti-Entropy),形象地说明了其特点:在一个有界网络中,每个节点都随机地与其他节点通信,经过一番杂乱无章的通信,最终所有节点的状态都会达成一致。

    20.1.6. 一致性Hash

    一致性哈希算法(Consistent Hashing Algorithm)是一种分布式算法,常用于负载均衡。

    具有以下特点:

    • 平衡性(Balance): 哈希的结果能够均匀分布到所有的缓冲中。
    • 单调性(Monotonicity): 如果有新的缓冲加入到系统中,哈希的结果能够保证原有已分配的内容可以被映射到原本所在的缓存或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。例如,简单求余算法hash(object)%N是不满足单调性要求的。
    • 平滑性(Smoothness): 缓存服务器数目的平滑改变和缓存对象的平滑改变是一致的。

    实现原理:

    1. 建构环形 hash 空间
    2. 把需要缓存的内容(对象)映射到 hash 空间
    3. 把服务器(节点)映射到 hash 空间
    4. 把对象映射到服务节点,沿环顺时针寻找

    为了解决分布不够均匀和多米诺宕机的问题,引入了虚拟节点:
    虚拟节点( virtual node )是实际节点在 hash 空间的复制品( replica ),一个实际节点对应了若干个“虚拟节点”,“虚拟节点”在 hash 空间中以 hash 值排列。
    映射关系也变为了:对象 -> 虚拟节点 -> 实际节点。

    21. JAVA 算法

    21.1.1. 二分查找

    21.1.2. 冒泡排序算法

    21.1.3. 插入排序算法

    21.1.4. 快速排序算法

    21.1.1. 希尔排序算法

    定义一个间隔序列来表示排序过程中进行比较的元素之间有多远的间隔,每次将具有相同间隔的数分为一组,进行插入排序;间隔序列里的值单向递减,最终到1,例如:[n/2, n/4, /n8, …, 1]。
    空间复杂度<O(n^2),具体依赖于间隔序列函数。不稳定排序。

    21.1.2. 归并排序算法

    21.1.3. 桶排序算法

    桶排序(Bucket sort),将数组按照递增的数值区间依次分到有限数量的桶里。每个桶再分别排序(其他排序算法或者递归桶排序),最后依次把各个桶中的记录列出来就得到有序序列。
    当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间O(n),但空间复杂度比较高。

    桶排序和基数排序都是分配排序(Distributive Sort),分配排序的基本思想:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。所以,他们不受到O(n log n)下限的影响。

    21.1.4. 基数排序算法

    将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
    实际操作时,是将依次将个位、十位、百位的相同的数放入同一个桶中,高位不足补零。然后再从桶中收集元素。
    在大多数情况下,效率为O(n*logn),最好情况是O(n)。空间复杂度较高。

    21.1.5. 剪枝算法

    21.1.6. 回溯算法

    21.1.7. 最短路径算法

    21.1.8. 最大子数组算法

    21.1.9. 最长公共子序算法

    21.1.10. 最小生成树算法

    22. 数据结构

    22.1.1. 栈(stack)

    22.1.2. 队列(queue)

    22.1.3. 链表(Link)

    22.1.4. 散列表(Hash Table)

    常用的构造散列函数的方法有:

    • 直接定址法: 取关键字或关键字的某个线性函数值为散列地址
    • 平方取值法: 取关键字平方后的中间几位为散列地址。
    • 折叠法: 将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
    • 除留余数法: 取关键字被 n 除后所得的余数为散列地址。
    • 随机数法: 选择一个随机函数,取关键字的随机函数值为它的散列地址。

    22.1.5. 排序二叉树

    22.1.6. 红黑树

    22.1.7. B-TREE

    22.1.8. 位图

    23. 加密算法

    23.1.1. AES

    23.1.2. RSA

    23.1.3. CRC

    23.1.4. MD5

    24. 分布式缓存

    24.1.1. 缓存雪崩

    缓存雪崩是因为缓存大面积地失效,从而导致所有请求都会去查数据库,导致数据库、CPU和内存负载过高,甚至宕机,进而造成一系列连锁反应,整个系统崩溃。

    24.1.2. 缓存穿透

    缓存穿透是指用户查询数据,在数据库和缓存都没有,这就导致进行了两次无用的查询。

    24.1.3. 缓存预热

    缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。

    24.1.4. 缓存更新

    24.1.5. 缓存降级

    当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然 需要保证核心服务还是可用的,即使是有损服务。

    25. HADOOP

    25.1.1. 概念

    HADOOP 就是一个大数据解决方案。它提供了一套分布式系统基础架构。 核心内容包含 hdfs 和 mapreduce,hdfs 是提供数据存储的,mapreduce 是方便数据计算的。

    25.1.2. HDFS

    • 整个 Hadoop 集群中只有一个 NameNode。 它负责管理 HDFS 的目录树和相关的文件元数据信息,监控各个 DataNode 的健康状态并管理。
    • Secondary NameNode 会定期合并 fsimage(HDFS 元数据镜像文件) 和 edits (HDFS 文件改动日志), 并传输给 NameNode。
    • DataNode 负责实际的数据存储, 并将数据信息定期 汇报给 NameNode。

    25.1.3. MapReduce

    • JobTracker 主要负责资源监控和作业调度。它监控所有 TaskTracker 与作业的健康状况,同时会跟踪任务的执行进度、资源使用量等信息,并将这些信息告诉任务调度器。
    • TaskTracker 会周期性地通过 Heartbeat 将本节点上资源的使用情况和任务的运行进度汇报给 JobTracker, 同时接收 JobTracker 发送过来的命令并执行相应的操作(如启动新任务、 杀死任务等)。
    • Task 分为 Map Task 和 Reduce Task 两种, 均由 TaskTracker 启动。

    26. SPARK

    Spark 提供了一个全面、统一的框架用于管理各种有着不同性质(文本数据、图表数据等)的数据集和数据源(批量数据或实时的流数据)的大数据处理的需求。

    Spark 的核心是建立在统一的抽象弹性分布式数据集(Resiliennt Distributed Datasets,RDD)之上的,这使得 Spark 的各个组件,如SQLContext、HiveContext、StreamingContext可以无缝地进行集成,能够在同一个应用程序中完成大数据处理。

    27. STORM

    Storm 是一个免费并开源的分布式实时计算系统。利用 Storm 可以很容易做到可靠地处理无限的 数据流,像 Hadoop 批量处理大数据一样,Storm 可以实时处理数据。

    在 Storm 中,一个实时应用的计算任务被打包作为 Topology 发布,计算任务 Topology 是由多个 Spouts 和 Bolts,通过数据流(Stream)连接起来的图。spout 会从外部数据源中读取数据,然后转 换为 topology 内部的源数据,接着,数据以 tuple(一组消息的单元) 的方式发送到 bolt,bolt 执行用户想要的操作,多个 bolt 可以相互连接起来,每个bolt可以有多个输入和输出。

    28. YARN

    28.1.1. 概念

    YARN 是一个资源管理、任务调度的框架,主要包含三大模块: ResourceManager(RM)、 NodeManager(NM)、ApplicationMaster(AM)。
    其中,ResourceManager 负责所有资源的监控、分配和管理; ApplicationMaster 负责每一个具体应用程序的调度和协调; NodeManager 负责每一个节点的维护。对于所有的 applications,RM 拥有绝对的控制权和对资源的分配权。而每个 AM 则会和 RM 协商资源,同时和 NodeManager 通信来执行和监控 task。

    29. 机器学习

    29.1.1. 决策树

    在进行逐步应答过程中,典型的决策树分析会使用分层变量或决策节点,例如,可将一个给定用户分类成信用可靠或不可靠。

    29.1.2. 随机森林算法

    随机森林算法通过使用多个带有随机选取的数据子集的树(tree)改善了决策树的精确性。例如,在基因表达层面上考察大量与乳腺癌复发相关的基因,并计算出复发风险。

    29.1.3. 逻辑回归

    回归可以勾画出因变量与一个或多个因变量之间的状态关系。例如,将垃圾邮件和非垃圾邮件进行区分。

    29.1.4. SVM

    29.1.5. 朴素贝叶斯

    朴素贝叶斯分类器用于计算可能条件的分支概率。每个独立的特征都是「朴素」或条件独立的,因此它们不会影响别的对象。 例如,在一个装有共 5 个黄色和红色小球的罐子里,连续拿到两个黄色小球的概率是多少?从图中最上方分支可见,前后抓取两个黄色小球的概率为 1/10。朴素贝叶斯分类器可以计算多个特征的联合条件概率。

    29.1.6. K 最近邻算法

    29.1.7. K 均值算法

    29.1.8. Adaboost 算法

    29.1.9. 神经网络

    在任意神经网络中,每个神经元都通过 1 个或多个隐藏层来将很多输入转换成单个输出。循环神经网络(RNN)会将值进一步逐层传递,让逐层学习成为可能。换句话说,RNN 存在某种形式的记忆,允许先前的输出去影响后面的输入。

    29.1.10. 马尔可夫

    显马尔可夫过程是完全确定性的——一个给定的状态经常会伴随另一个状态。交通信号灯就是一个例子。相反,隐马尔可夫模型通过分析可见数据来计算隐藏状态的发生。随后,借助隐藏状态分析,隐马尔可夫模型可以估计可能的未来观察模式。例如,高或低气压的概率(这是隐藏状态)可用于预测晴天、雨天、多云天的概率。

    30. 云计算

    30.1.1. SaaS

    SaaS 是 Software-as-a-Service(软件即服务)

    30.1.2. PaaS

    云计算时代把服务器平台或者开发环境作为服务进行提供就成为了 PaaS(Platform as a Service,平台即服务)。

    30.1.3. IaaS

    IaaS(Infrastructure as a Service),即基础设施即服务。
    提供给消费者的服务是对所有设施的利用,包括处理、存储、网络和其它基本的计算资源,用户能够部署和运行任意软件,包括操作系统和应用程序。

    30.1.4. Docker

    通过 Docker 我们可以将程序运行的环境也纳入到版本控制中,排除因为环境造成开发环境和生产环境环境不同运行结果的可能。

    docker 创建新进程时传入 CLONE_NEWPID 实现的进程隔离,也就是使用 Linux 的命名空间实现 进程的隔离,Docker 容器内部的任意进程都对宿主机器的进程一无所知。
    Docker 整个网络部分的功能都是通过 Docker 拆分出来的 libnetwork 实现的,它提供了一个连接不同容器的实现,同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。
    Control Groups(简称 CGroups)能够隔离宿主机器上的物理资源,例如 CPU、内存、磁盘 I/O 和网 络带宽。

    当镜像被 docker run 命令创建时就会在镜像的最上层添加一个可写的层,也就是容器层。容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层。

    30.1.5. Openstack和Kubernetes

    OpenStack作为一个开源的云计算平台,利用虚拟化技术和底层存储服务,提供了可扩展,灵活,适应性强的云计算服务。
    简单的说,虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的CPU、内存、IO硬件资源,但逻辑上虚拟机之间是相互隔离的。宿主机一般使用hypervisor程序实现硬件资源虚拟化,并提供给客户机使用。

    Kubernetes简称K8s,是容器管理编排引擎,那么底层实现自然是容器技术。它支持自动化部署、大规模可伸缩、应用容器化管理。在Kubernetes中,我们可以创建多个容器,每个容器里面运行一个应用实例,然后通过内置的负载均衡策略,实现对这一组应用实例的管理、发现、访问,而这些细节都不需要运维人员去进行复杂的手工配置和处理。
    容器是一种轻量级、可移植、自包含的软件打包技术,打包的应用程序可以在几乎任何地方以相同的方式运行。以容器典型代表docker为例,docker起源于2013年3月,是基于LXC为基础构建的容器引擎,通过namespace和cgourp实现了资源隔离和调配,使用分层存储来构建镜像。它基于Google公司推出的Go语言实现。docker相比KVM虚拟化技术最明显的特点就是启动快,资源占用小。虚拟化启动虚拟机是分钟级别的,而docker是秒级别的。

    展开全文
  • java面试核心知识点,283页pdf,直指阿里P7
  • JAVA面试核心知识点整理,比较适合1-3年开发者。内容涵盖面较广,讲解详细,跳槽之前好好看看这些吧!
  • Java面试核心知识点汇总

    千次阅读 2020-03-12 15:57:24
    本篇博客是对我的专栏《Java面试复习笔记》的一...慕课网《Java并发核心知识体系精讲》 慕课网《剑指Java面试-Offer直通车》 慕课网《高薪之路–Java面试题精选集》 慕课网《面试官系统精讲Java源码及大厂真题》 拉...

    最近更新:2020年3月22日21:28:47

    本篇博客是对我的专栏《Java面试复习笔记》的一个索引
    (内容持续更新!!!)(后续学习了继续补充)
    专栏的内容包括了我在诸多付费课程中学习的关于Java面试的笔记和总结
    其中的课程包含了

    希望这个专栏除了能帮助我自己构建知识体系,掌握面试的知识,同时也能帮助和我一样在学校没有学到太多实用东西想要找大厂工作的同学

    并发编程篇

    你必须了解的Java并发核心知识

    直达链接

    内容包含->

    • 进程和线程的区别
    • Java进程与线程的关系
    • 有多少种实现线程的方法?
    • 实现Runnable接口和继承Thread类哪种方式更好?
    • 一个线程两次调用start()方法会出现什么情况?为什么?
    • 既然start()方法会调用run方法,为什么我们选择调用start方法而不是直接调用run方法呢?
    • 如何停止线程?
    • 如何处理不可中断的阻塞
    • 线程有哪几种状态?生命周期是什么?
    • 用程序实现两个线程交替打印(0-100)的奇偶数
    • 什么是生产者消费者模式
    • 手写生产者消费者模式
    • 为什么wait方法需要在同步代码块内使用,而sleep不需要
    • 为什么线程通信的方法wait、notify、notifyAll被定义在Object类中?而slepp方法被定义在Thread类中?
    • wait方法是属于Object对象的,那调用Thread.wait()会怎么样
    • 如何选择用notify还是notifyAll
    • notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失- 败怎么办?
    • suspend和resume来阻塞线程可以吗?为什么?
    • wait/notify、sleep异同
    • yield和sleep的区别
    • 在join期间,线程处于哪种线程状态?
    • 守护线程和普通线程的区别
    • 我们是否需要给线程设置守护线程?
    • run方法是否可以抛出异常?
    • 如何全局处理异常
    • 什么是多线程的上下文切换
    • 为什么多线程会带来性能问题?
    • 何时会导致密集的上下文切换
    • 单例模式的作用和适用场景
    • 单例模式的八种写法及相关知识点
    • 工作中哪种单例模式的实现最好
    • 讲一讲什么是Java内存模型
    • 关于死锁你知道多少?

    面试遇到Java并发容器,我能和面试官聊上20分钟

    直达链接

    内容包含->

    • CopyOnWriteArrayList
      • 常见问题
      • 源码分析
      • 面试题
    • concurrentHashMap
      • 常见问题
      • 源码分析
      • 面试题

    讲一讲什么是Java内存模型

    直达链接

    内容包含->

    • 为什么会有Java内存模型?
    • 辨析JVM内存结构、Java内存模型、Java对象模型
      • JVM内存结构
      • Java对象模型
      • Java内存模型
    • 重排序
      • 例子演示:
      • 什么是重排序
      • 重排序的好处
      • 重排序的3种情况
    • 可见性
      • 什么是可见性问题
      • 为什么会有可见性问题
      • JMM主内存与本地内存的关系
      • happens-before规则有哪些?
      • volatile是什么
      • 什么时候适合用volatile
      • volatile的作用
      • volatile与synchronized的关系
    • 原子性
      • 什么是原子性
      • Java中的原子操作有哪些
      • 生成对象的过程是不是原子操作?

    彻底搞懂死锁的前世今生

    直达链接

    内容包含–>

    • 死锁是什么,有什么危害?
    • 写一个死锁的例子
      • 案例一:必然发生死锁
      • 案例二:两个账户转账
      • 案例三:多人多次转账
    • 发生死锁必须满足哪些条件
    • 如何定位死锁
    • 有哪些解决死锁问题的策略?
      • 线上发生死锁怎么办
      • 常见修复策略
    • 哲学家就餐问题
      • 问题描述
      • 代码演示
      • 多种解决方案
      • 改变一个哲学家拿叉子的顺序的实现
    • 工程中如何避免死锁

    通过单例模式的8种写法搞定单例模式面试

    直达链接

    内容包含–>

    • 饿汉式(静态常量)(可用)
    • 饿汉式(静态代码块)(可用)
    • 懒汉式(线程不安全)(不可用)
    • 懒汉式(线程安全)(不推荐)
    • 懒汉式(加锁,线程不安全)(不可用)
    • 双重检查(推荐面试使用)(可用)
    • 静态内部类(推荐用)(可用)
    • 枚举(推荐用)(可用)(生产中最佳写法)

    ThreadLocal你知道多少?

    直达链接
    内容包含->

    • ThreadLocal典型应用场景
      • 场景1:每个线程需要一个独享的对象
      • 场景2:当前用户信息需要被线程内所有方法共享
    • ThreadLocal的两个作用
    • 两种初始化方法使用场景
    • 使用ThreadLocal的好处
    • ThreadLocal与Thread的关系
    • ThreadLocal的重要方法
    • ThreadLocal注意点
    • ThreadLocal 为什么会发生内存溢出?
      如何解决内存溢出

    不得不说的“锁”事

    直达链接

    内容包含->

    • Lock接口
      • 简介
      • 为什么需要Lock
      • 方法介绍
      • 可见性保证
    • 锁的分类
      • 乐观锁和悲观锁
        • 为什么会诞生非互斥同步锁(乐观锁)
        • 什么是乐观锁和悲观锁
        • 典型例子
        • 开销对比
        • 使用场景
      • 可重入锁和非可重入锁
      • 公平锁和非公平锁
        • 什么是公平和非公平
        • 为什么要有非公平锁
        • 公平的情况(以ReentrantLock 为例)
        • 不公平的情况(以ReentrantLock 为例)
        • 特例
        • 对比非公平和公平的优缺点
      • 共享锁和排它锁
        • 什么是共享锁和排它锁
        • 读写锁的作用
        • 读写锁的规则
        • ReetrantReadWriteLock的具体用法
        • 读锁插队策略
        • 升降级策略
      • 自旋锁和阻塞锁
        • 为什么需要自旋锁
        • 自旋锁缺点
        • 代码演示
        • 自旋锁的适用场景
      • 可中断锁和不可中断锁
    • 写代码时如何优化锁并提高并发性能

    关于线程池你知道多少?

    直达链接
    内容包含->

    • 线程池的好处
    • 线程池适用场合
    • 线程池的创建
      • 线程池的构造函数的参数
      • 添加线程规则
      • 增减线程的特点
      • 线程池应该手动创建还是自动创建
    • 几种常见线程池
    • 如何正确停止一个线程池
    • 任务太多,怎么拒绝
      • 拒绝时机
      • 拒绝策略

    什么是CAS?

    直达链接
    内容包含->

    • 什么是CAS
    • 演示CAS核心原理
    • CAS应用场景
    • CAS的缺点

    再次认识下final关键字和不变性

    直达链接
    内容包含->

    • 什么是不变性
    • final的作用
    • final的三种用法
      • final修饰变量
      • final修饰方法
      • final修饰类
    • 注意点
    • 不变性与final的关系
    • 栈封闭技术
    • 面试题

    Java基础篇

    谈谈对Java平台的理解

    直达链接

    内容包含->

    • Java显著特征
    • Java的特性
    • Java代码执行过程
    • Java是解析运行的吗?

    String是如何实现的?有哪些重要方法?

    直达链接
    内容包含->

    • String是如何实现的?有哪些重要方法?
      • String是如何实现的?
      • 常用方法
    • 扩展
      • 为什么String类型要用final修饰?
      • equals和‘==’的区别是什么?
      • String 和 StringBuilder、StringBuffer 有什么区别?
      • String 的intern()方法有什么含义?
      • String类型在JVM中如何存储的?编译器做过哪些优化?

    面向对象可以解释下吗?都有哪些特性?

    直达链接
    内容包含->

    • 面对对象思想
    • 关于封装
    • 关于继承
    • 关于多态
    • 关于覆盖
    • 关于重载

    Java中的Exception和Error有什么区别?

    直达链接

    内容包含->

    • 概念
    • Exception分类
    • NoClassDefFoundError 和 ClassNotFoundException 有什么区别?
    • Throwable、Error和Exception之间的层次
    • 分析异常处理的不当
      • 案例一
      • 分析
      • 案例二
      • 分析
    • finally相关考点
      • finally不会被执行的情况
      • finally执行对结果造成的影响

    int和Integet有什么区别?

    直达链接
    内容包含->

    • int和Integet有什么区别
    • Java的8中原始数据类型和对应的包装类
    • Java的自动拆装箱

    强引用、软引用、弱引用、幻象引用有什么区别?

    直达链接

    抽象类和接口有什么区别?

    直达链接
    内容包含->

    • 概念
    • 区别
    • 接口和抽象类如何选择?分别在什么情况下使用?
    • JDK8为什么在接口中出现default类型的方法?

    JDK,JRE和JVM的区别与联系有哪些?

    直达链接
    内容包含->

    • 概念
    • 区别与联系
    • Java语言的平台无关性是如何实现的?
    • Java语言是编译型还是解释型语言?

    数据库篇

    一篇文章搞懂SQL在Oracle和MySQL中是如何执行的

    直达链接
    内容包含->

    • Oracle中的SQL是如何执行的
      • Oracle中应该多使用硬解析还是软解析呢?
    • MySQL中的SQL是如何执行的
      • MySQL与Oracle执行的区别

    算法篇

    动图加代码 轻松搞定十大排序算法

    直达链接
    内容包含->

    • 各类排序算法的对比与分类
    • 选择排序
      • 单向选择
      • 双向选择
    • 插入排序
      • 依次交换
      • 依次覆盖
    • 冒泡排序
      • 常规冒泡
      • 优化冒泡
    • 希尔排序
    • 归并排序
    • 快速排序
      • 普通快速排序
      • 随机快速排序
      • 双路快速排序
      • 三路快速排序
    • 堆排序
      • 优先队列
      • 原地堆
    • 计数排序
    • 桶排序
    • 基数排序

    leetcode高频题笔记之数组与矩阵

    直达链接
    内容包含->

    • 移动零
    • 盛最多水的容器
    • 爬楼梯
    • 两数之和
    • 三数之和
    • 删除排序数组中的重复项
    • 旋转数组
    • 合并两个有序数组
    • 加一
    • 重塑矩阵
    • 最大连续一的个数
    • 搜索二维矩阵II
    • 有序矩阵中第K小的元素
    • 寻找重复数

    leetcode高频题笔记之链表

    直达链接

    内容包含->

    • 环形链表
    • 环形链表II
    • 合并两个有序链表
    • 反转链表
    • 两两交换链表中的节点
    • 相交链表
    • 删除排序链表中的重复元素
    • 删除链表的倒数第N个节点
    • 两数相加II
    • 回文链表
    • 奇偶链表
    • 分割链表

    leetcode高频题笔记之栈和队列

    直达链接

    内容包含->

    • 有效的括号
    • 最小栈
    • 用栈实现队列
    • 用队列实现栈
    • 柱形图中的最大面积
    • 接雨水
    • 每日温度
    • 下一个更大元素II

    leetcode高频题笔记之哈希表

    直达链接

    内容包含->

    • 有效的字母异位词
    • 字母异位词分组
    • 存在重复元素
    • 最长和谐子序列
    • 最长连续序列

    leetcode高频题笔记之树的遍历

    直达链接

    内容包含->

    • 二叉树的中序遍历(3种实现)
      • 递归实现
      • 状态标记法迭代实现
      • 普通迭代实现
    • 二叉树的前序遍历(3种实现)
      • 递归实现
      • 状态标记法迭代实现
      • 普通迭代实现
    • 二叉树的后序遍历(3种实现)
      • 递归实现
      • 状态标记法迭代实现
      • 普通迭代实现
    • N叉树的前序遍历
      • 递归实现
      • 迭代实现
    • N叉树的后序遍历
      • 递归实现
      • 迭代实现

    leetcode高频题笔记之递归

    直达链接
    内容包含->

    • 括号生成
    • 验证二叉搜索树
    • 二叉树的最大高度
    • 二叉树的最小深度
    • 翻转二叉树
    • 二叉搜索树的最近公共祖先
    • 二叉树的最近公共祖先
    • 从前序与中序遍历序列构造二叉树
    • 从中序与后序遍历序列构造二叉树

    leetcode高频题笔记之分治与回溯

    直达链接
    内容包含->

    • Pow(x,n)
    • 子集
    • 电话号码的字母组合
    • 组合
    • 全排列
    • 全排列II
    • N皇后

    简历篇

    怎么将个人简历部署在github上?

    直达链接

    如何解析自己的域名到博客主页

    直达链接

    展开全文
  • java面试各大知识点,非常齐全。jvm内存分区,gc算法,类加载机制。并发编程,并发编程各大容器,锁。框架spring mybatis原理,组件介绍。架构设计有设计模式,负载均衡。网络有协议,java 高并发io框架netty。数据...
  • 这部分主要从Java常用的Java基础知识展开详细的介绍,具体包含Java的集合、异常分类及处理、反射机制、注解、内部类、泛型、序列化。 1.集合 Java的集合类被定义在Java.util包中,主要有4种集合,分别为List、Queue...

    这部分主要从Java常用的Java基础知识展开详细的介绍,具体包含Java的集合、异常分类及处理、反射机制、注解、内部类、泛型、序列化。

    1.集合

    Java的集合类被定义在Java.util包中,主要有4种集合,分别为List、Queue、Set和Map,每种集合的具体分类如图2-1所示。
    在这里插入图片描述

    1.1 List

    List是一个底层是数组,有序,可重复的Collection 一共有三个实现类,分别是ArrayList、Vector和LinkedList。

    1. ArrayList:基于数组实现,增删慢,查询快,线程不安全
      ArrayList是使用最广泛的List实现类,其内部数据结构基于数组实现,提供了对List的增加(add)、删除(remove)和访问(get)功能。
      ArrayList的缺点是对元素必须连续存储,当需要在ArrayList的中间位置插入或者删除元素时,需要将待插入或者删除的节点后的所有元素进行移动,其修改代价较高,因此,ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作。
      ArrayList不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个新的更大的数组并将数组中已有的数据复制到新的数组中。
    2. Vector:基于数组实现,增删慢,查询快,线程安全
      Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector支持线程同步,即同一时刻只允许一个线程对Vector进行写操作(新增、删除、修改),以保证多线程环境下数据的一致性,但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率在整体上比ArrayList低。
    3. LinkedList:基于双向链表实现,增删快,查询慢,线程不安全
      LinkedList采用双向链表结构存储元素,在对LinkedList进行插入和删除操作时,只需在对应的节点上插入或删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,数据改动较小,因此随机插入和删除效率很高。但在对LinkedList进行随机访问时,需要从链表头部一直遍历到该节点为止,因此随机访问速度很慢。除此之外,LinkedList还提供了在List接口中未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈、队列或双向队列使用。

    1.2 Queue

    Queue是队列结构,Java中的常用队列如下。
    ◎ ArrayBlockingQueue:基于数组数据结构实现的有界阻塞队列。
    ◎ LinkedBlockingQueue:基于链表数据结构实现的有界阻塞队列。
    ◎ PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
    ◎ DelayQueue:支持延迟操作的无界阻塞队列。
    ◎ SynchronousQueue:用于线程同步的阻塞队列。
    ◎ LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列。
    ◎ LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列。

    1.3 Set --不可重复

    Set核心是独一无二的性质,适用于存储无序且值不相等的元素。对象的相等性在本质上是对象的HashCode值相同,Java依据对象的内存地址计算出对象的HashCode值。如果想要比较两个对象是否相等,则必须同时覆盖对象的hashCode方法和equals方法,并且hashCode方法和equals方法的返回值必须相同。

    1. HashSet:HashTable实现,无序
      HashSet存放的是散列值,它是按照元素的散列值来存取元素的。元素的散列值是通过元素的hashCode方法计算得到的,HashSet首先判断两个元素的散列值是否相等,如果散列值相等,则接着通过equals方法比较,如果equls方法返回的结果也为true, HashSet就将其视为同一个元素;如果equals方法返回的结果为false, HashSet就不将其视为同一个元素。
    2. TreeSet:二叉树实现
      TreeSet基于二叉树的原理对新添加的对象按照指定的顺序排序(升序、降序),每添加一个对象都会进行排序,并将对象插入二叉树指定的位置。
      Integer和String等基础对象类型可以直接根据TreeSet的默认排序进行存储,而自定义的数据类型必须实现Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。若覆写compare函数,则在升序时在this.对象小于指定对象的条件下返回-1,在降序时在this.对象大于指定对象的条件下返回1.
    3. LinkHashSet:HashTable实现数据存储,双向链表记录顺序
      LinkedHashSet在底层使用LinkedHashMap存储元素,它继承了HashSet,所有的方法和操作都与HashSet相同,因此LinkedHashSet的实现比较简单,只提供了4个构造方法,并通过传递一个标识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其他相关操作与父类HashSet相同,直接调用父类HashSet的方法即可。

    1.4 Map

    1. HashMap:数组+链表存储数据,线程不安全
      HashMap基于键的HashCode值唯一标识一条数据,同时基于键的HashCode值进行数据的存取,因此可以快速地更新和查询数据,但其每次遍历的顺序无法保证相同。HashMap的key和value允许为null。
      HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一致。如果需要满足线程安全的条件,则可以用Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
      HashMap的数据结构如图2-2所示,其内部是一个数组,数组中的每个元素都是一个单向链表,链表中的每个元素都是嵌套类Entry的实例,Entry实例包含4个属性:key、value、hash值和用于指向单向链表下一个元素的next。
      在这里插入图片描述
      HashMap常用的参数如下。
      ◎ capacity:当前数组的容量,默认为16,可以扩容,扩容后数组的大小为当前的两倍,因此该值始终为2n。
      ◎ loadFactor:负载因子,默认为0.75。
      ◎ threshold:扩容的阈值,其值等于capacity×loadFactor。

    HashMap在查找数据时,根据HashMap的Hash值可以快速定位到数组的具体下标,但是在找到数组下标后需要对链表进行顺序遍历直到找到需要的数据,时间复杂度为O(n)。
    为了减少链表遍历的开销,Java 8对HashMap进行了优化,将数据结构修改为数组+链表或红黑树。在链表中的元素超过8个以后,HashMap会将链表结构转换为红黑树结构以提高查询效率,因此其时间复杂度为O(log N)。Java 8 HashMap的数据结构如图2-3所示。
    在这里插入图片描述

    1. ConcurrentHashMap:分段锁实现,线程安全
      与HashMap不同,ConcurrentHashMap采用分段锁的思想实现并发操作,因此是线程安全的。ConcurrentHashMap由多个Segment组成(Segment的数量也是锁的并发度),每个Segment均继承自ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个Segment,这样只要保证每个Segment都是线程安全的,也就实现了全局的线程安全。ConcurrentHashMap的数据结构如图2-4所示。
      在这里插入图片描述
      在ConcurrentHashMap中有个concurrencyLevel参数表示并行级别,默认是16,也就是说ConcurrentHashMap默认由16个Segments组成,在这种情况下最多同时支持16个线程并发执行写操作,只要它们的操作分布在不同的Segment上即可。并行级别concurrencyLevel可以在初始化时设置,一旦初始化就不可更改。ConcurrentHashMap的每个Segment内部的数据结构都和HashMap相同。

    Java 8在ConcurrentHashMap中引入了红黑树,具体的数据结构如图
    在这里插入图片描述

    1. HashTable:线程安全
      HashTable是遗留类,很多映射的常用功能都与HashMap类似,不同的是它继承自Dictionary类,并且是线程安全的,同一时刻只有一个线程能写HashTable,并发性不如ConcurrentHashMap。
    2. TreeMap:基于二叉树数据结构
      TreeMap基于二叉树数据结构存储数据,同时实现了SortedMap接口以保障元素的顺序存取,默认按键值的升序排序,也可以自定义排序比较器。
      TreeMap常用于实现排序的映射列表。在使用TreeMap时其key必须实现Comparable接口或采用自定义的比较器,否则会抛出java.lang.ClassCastException异常。
    3. LinkedHashMap:基于HashTable数据结构,使用链表保存插入顺序
      LinkedHashMap为HashMap的子类,其内部使用链表保存元素的插入顺序,在通过Iterator遍历LinkedHashMap时,会按照元素的插入顺序访问元素。

    2. 反射机制

    2.1 反射机制的概念

    反射机制指在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能被称为Java语言的反射机制。

    // 1.获取Person类的class类
    Class classz = Class.forName("hello.java.reflect.Person");
    // 2. 获取person类的所有方法的信息
    Method[] method = classz.getDeclaredMethods();
    for(Method m:method){
        System.out.println(m.toString());
    }
    //获取Person类的所有成员的属性信息
    Field[] field = classz.getDeclaredFields();
    // 获取Person类的所有构造方法的信息
    Constructor[] constructor = classz.getDeclaredConstructors();
    

    Method提供了关于类或接口上某个方法及如何访问该方法的信息,那么在运行的代码中如何动态调用该方法呢?答案就通过调用Method的invoke方法。我们通过invoke方法可以实现动态调用,比如可以动态传入参数及将方法参数化。具体过程为:获取对象的Method,并调用Method的invoke方法,如下所述。
    (1)获取Method对象:通过调用Class对象的getMethod(Stringname, Class<? >… parameterTypes)返回一个Method对象,它描述了此Class对象所表示的类或接口指定的公共成员方法。name参数是String类型,用于指定所需方法的名称。parameterTypes参数是按声明顺序标识该方法的形参类型的Class对象的一个数组,如果parameterTypes为null,则按空数组处理。
    (2)调用invoke方法:指通过调用Method对象的invoke方法来动态执行函数

    3 序列化

    Java对象在JVM运行时被创建、更新和销毁,当JVM退出时,对象也会随之销毁,即这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,我们常常需要将对象及其状态在多个应用之间传递、共享,或者将对象及其状态持久化,在其他地方重新读取被保存的对象及其状态继续进行处理。这就需要通过将Java对象序列化来实现。

    在使用Java序列化技术保存对象及其状态信息时,对象及其状态信息会被保存在一组字节数组中,在需要时再将这些字节数组反序列化为对象。注意,对象序列化保存的是对象的状态,即它的成员变量,因此类中的静态变量不会被序列化。

    对象序列化除了用于持久化对象,在RPC(远程过程调用)或者网络传输中也经常被使用。

    展开全文
  • java面试各大知识点,非常齐全。jvm内存分区,gc算法,类加载机制。并发编程,并发编程各大容器,锁。框架spring mybatis原理,组件介绍。架构设计有设计模式,负载均衡。网络有协议,java 高并发io框架netty。数据...
  • Java源文件(.java文件)通过编译器被编译成.class文件(字节码文件),.class文件又被JVM中的解释器编译成机器吗在不同操作系统上运行(Windows,Mac,Linux)。每种操作系统的解释器都是不同的,但是基于解释器的...

    1 JVM的运行机制

    我们知道JVM是用于运行Java字节码的虚拟机,主要包括一套字节码指令集、一组程序计数器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。JVM运行在操作系统之上,不与硬件设备直接交互。

    Java源文件(.java文件)通过编译器被编译成.class文件(字节码文件),.class文件又被JVM中的解释器编译成机器吗在不同操作系统上运行(Windows,Mac,Linux)。每种操作系统的解释器都是不同的,但是基于解释器的虚拟机是一样的,这也是为什么Java能够跨平台的原因。在一个Java进程开始运行后,虚拟机就开始实例化了,有多个进程启动就会实例化多个虚拟机实例。进程退出或者关闭,则虚拟机实例消亡,在多个虚拟机实例之间不能共享数据。

    Java虚拟机包括一个类加载器子系统、运行时数据区、执行引擎和本地接口库。本地接口库通过调用本地方法库与操作系统进行交互。

    2.JVM的内存区域

    JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区)、线程共享区域(堆,方法区)和直接内存

    线程私有区域:生命周期与线程相同,随线程的启动而创建,结束而销毁。该部分主要包括程序计数器、虚拟机栈和本地方法区。

    1.     程序计数器:是一块很小的内存空间,用来存储当前运行的线程所执行的字节码的行号指示器。每个运行中的线程中都有一个独立的程序计数器,在方法执行时,该方法的程序计数器记录的是实时虚拟机字节码指令的地址;如果该方法执行的是native方法,则程序计数器的值为空。这是唯一一个没有内存溢出的区域(out of memory)
    2.     虚拟机栈:描述Java方法的执行过程。生命周期与线程一致,主要描述的是Java方法的执行过程的内存模型,每个方法在执行的时候会创建一个栈桢,这个栈桢会存储局部变量表、操作数栈、动态链接、方法出口等信息。同时它还用来保持部分运行时数据及其数据结构,处理动态链接方法的返回值和异常分派。 栈桢用来记录方法的执行过程,在方法被执行时虚拟机为其创建一个与之对应的栈桢,方法的执行和返回对应栈栈在虚拟机中的入栈和出栈,
    3.    本地方法区(栈):与虚拟机栈类似,区别是虚拟机栈为执行的Java方法服务;本地方法栈为native方法服务。

    线程共享区域:随虚拟机的启动而创建,随虚拟机的关闭而销毁。

    1. 堆:它是JVM内存管理中最大的一块。在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。由于现代JVM采用分代收集算法,因此Java堆从GC(Garbage Collection,垃圾回收)的角度还可以细分为:新生代、老年代和永久代。
    2. 方法区:也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行时常量池等数据。JVM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样JVM的垃圾收集器就可以像管理Java堆一样管理这部分内存。永久带的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少。 

              常量被存储在运行时常量池中,是方法区的一部分。静态变量也属于方法区的一部分。在类信息(Class文件)中不但保存了类的版本、字段、方法、接口等描述信息,还保存了常量信息。 

    直接内存;堆外内存。

    3.JVM的运行时内存(JVM堆)

    JVM的运行时内存也叫作JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占1/3堆空间,老年代默认占2/3堆空间,永久代占非常少的堆空间。新生代又分为Eden区、ServivorFrom区和ServivorTo区,Eden区默认占8/10新生代空间,ServivorFrom区和ServivorTo区默认分别占1/10新生代空间

    • 新生代:eden区、servivorTo区和ServivorFrom区。

         JVM新创建的对象(除了大对象外)会被存放在新生代,默认占1/3堆内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为Eden区、ServivorTo区和ServivorFrom区,如下所述。

    (1)Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为2KB~128KB,可通过XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。

    (2)ServivorTo区:保留上一次MinorGC时的幸存者。

    (3)ServivorFrom区:将上一次MinorGC时的幸存者作为这一次MinorGC的被扫描者。

    新生代的GC过程叫作MinorGC,采用复制算法实现,具体过程如下。

    (1)把在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区。如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由XX:MaxTenuringThreshold设置,默认为15),则将其复制到老年代,同时把这些对象的年龄加1;如果ServivorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为2KB~128KB的对象属于大对象,例如通过XX:PretenureSizeThreshold=2097152设置大对象为2MB,1024×1024×2Byte=2097152Byte=2MB),则也直接将其复制到老年代。

    (2)清空Eden区和ServivorFrom区中的对象

    (3)将ServivorTo区和ServivorFrom区互换,原来的ServivorTo区成为下一次GC时的ServivorFrom区。

    • 老年代

        老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫作MajorGC。在老年代,对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代空间不足或无法找到足够大的连续空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。

    MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放内存空间。

    因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。

    • 永久代

        永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久代。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载的Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多导致JVM内存不足而无法启动。

    需要注意的是,在Java 8中永久代已经被元数据区(也叫作元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机的内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,只和操作系统的内存有关。

    在Java 8中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用内存空间决定。

    4.垃圾回收与算法

    4.1 如何确认垃圾

    Java确认对象是否回收有两种方式:引用计数法和可达性分析,其中,引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法(GC Roots Tracing)来实现。

    1. 引用计数法:在Java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为对象添加一个引用时,引用计数加1;在为对象删除一个引用时,引进计数减1;如果一个对象的引用计数为0,则表示此刻该对象没有被引用,可以被回收。容易产生循环引用问题。循环引用指两个对象相互引用,导致它们的引用一直存在,而不能被回收
    2. 可达性分析法:为了解决引用计数法的循环引用问题,Java还采用了可达性分析来判断对象是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GC Roots对象作为起点向下搜索,如果在GC roots和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象要经过至少两次标记才能判定其是否可以被回收,如果在两次标记后该对象仍然是不可达的,则将被垃圾收集器回收。

    4.2 Java中常用的垃圾回收算法

    Java中常用的垃圾回收算法有标记清除(Mark-Sweep)、复制(Copying)、标记整理(Mark-Compact)和分代收集(Generational Collecting)这4种垃圾回收算法。

    • 标记清除算法

    标记清除算法是基础的垃圾回收算法,其过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除可回收的对象并释放其所占用的内存空间。

    由于标记清除算法在清理对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中可被回收的小对象居多,则会引起内存碎片化的问题,继而引起大对象无法获得连续可用空间的问题。

    • 复制算法

    复制算法是为了解决标记清除算法内存碎片化的问题而设计的。复制算法首先将内存划分为两块大小相等的内存区域,即区域1和区域2,新生成的对象都被存放在区域1中,在区域1内的对象存储满后会对区域1进行一次标记,并将标记后仍然存活的对象全部复制到区域2中,这时区域1将不存在任何存活的对象,直接清理整个区域1的内存即可。

    复制算法的内存清理效率高且易于实现,但由于同一时刻只有一个内存区域可用,即可用的内存空间被压缩到原来的一半,因此存在大量的内存浪费。同时,在系统中有大量长时间存活的对象时,这些对象将在内存区域1和内存区域2之间来回复制而影响系统的运行效率。因此,该算法只在对象为“朝生夕死”状态时运行效率较高。

    • 标记整理算法

    标记整理算法结合了标记清除算法和复制算法的优点,其标记阶段和标记清除算法的标记阶段相同,在标记完成后将存活的对象移到内存的另一端,然后清除该端的对象并释放内存。

    • 分代收集算法

    无论是标记清除算法、复制算法还是标记整理算法,都无法对所有类型(长生命周期、短生命周期、大对象、小对象)的对象都进行垃圾回收。因此,针对不同的对象类型,JVM采用了不同的垃圾回收算法,该算法被称为分代收集算法。分代收集算法根据对象的不同类型将内存划分为不同的区域,JVM将堆划分为新生代和老年代。新生代主要存放新生成的对象,其特点是对象数量多但是生命周期短,在每次进行垃圾回收时都有大量的对象被回收;老年代主要存放大对象和生命周期长的对象,因此可回收的对象相对较少。因此,JVM根据不同的区域对象的特点选择了不同的算法。目前,大部分JVM在新生代都采用了复制算法,因为在新生代中每次进行垃圾回收时都有大量的对象被回收,需要复制的对象(存活的对象)较少,不存在大量的对象在内存中被来回复制的问题,因此采用复制算法能安全、高效地回收新生代大量的短生命周期的对象并释放内存。JVM将新生代进一步划分为一块较大的Eden区和两块较小的Servivor区,Servivor区又分为ServivorFrom区和ServivorTo区。JVM在运行过程中主要使用Eden区和ServivorFrom区,进行垃圾回收时会将在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,然后清理Eden区和ServivorFrom区的内存空间 。

    老年代主要存放生命周期较长的对象和大对象,因而每次只有少量非存活的对象被回收,因而在老年代采用标记清除算法。

    在JVM中还有一个区域,即方法区的永久代,永久代用来存储Class类、常量、方法描述等。在永久代主要回收废弃的常量和无用的类。JVM内存中的对象主要被分配到新生代的Eden区和ServivorFrom区,在少数情况下会被直接分配到老年代。在新生代的Eden区和ServivorFrom区的内存空间不足时会触发一次GC,该过程被称为MinorGC。在MinorGC后,在Eden区和ServivorFrom区中存活的对象会被复制到ServivorTo区,然后Eden区和ServivorFrom区被清理。如果此时在ServivorTo区无法找到连续的内存空间存储某个对象,则将这个对象直接存储到老年代。若Servivor区的对象经过一次GC后仍然存活,则其年龄加1。在默认情况下,对象在年龄达到15时,将被移到老年代。

    5.垃圾收集器

    Java堆内存分为新生代和老年代:新生代主要存储短生命周期的对象,适合使用复制算法进行垃圾回收;老年代主要存储长生命周期的对象,适合使用标记整理算法进行垃圾回收。因此,JVM针对新生代和老年代分别提供了多种不同的垃圾收集器,针对新生代提供的垃圾收集器有Serial、ParNew、Parallel Scavenge,针对老年代提供的垃圾收集器有Serial Old、Parallel Old、CMS,还有针对不同区域的G1分区收集算法。

    重点说cms和g1垃圾收集器原理:

    CMS垃圾回收器:

    G1垃圾回收器:

    展开全文
  • 前言 本文是对Java程序员面试必备知识点的...针对这种情况,本文在讲解知识点时不拖泥带水,力求精简,详细介绍了Java程序员面试时常被问及的核心知识点。 本文将从目录、主要内容和对读者阅读本文的建议三部分.
  • 前言简介本书是对Java程序员面试必备知识点的总结,详细讲解了JVM原理、多线程、数据结构和算法、分布式缓存、设计模式等内容,希望读者能通过阅读本书对Java的基础原理有更深入、全面的理解。面试官通常会在短短两...
  • JVM(Java Virtual Machine)是用于运行Java字节码的虚拟机,。JVM运行在操作系统之上,不与硬件设备直接交互。Java虚拟机包括一个类加载子系统、运行时数据区、执行引擎和本地接口库。本地接口库通过调用本地方法库...
  • 网络 OSI七层网络模型 网络的七层架构从下到上主要包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 物理层 物理层主要定义物理设备标准,主要作用是传输比特流,具体做法是在发送端将1、0转化为...
  • 前言本文是对Java程序员面试必备知识点的总结,详细讲解了JVM原理、多线程、数据结构和算法、分布式缓存、设计模式等内容,希望读者能通过阅读本书对Java的基础原理有更深入、全面的理解。面试官通常会在短短两小时...
  • 每个技术人都有个大厂梦,我觉得这很正常,并不是饭后的谈资而是每个技术人的追求。...从业十多年,我从面试者变成面试官,在 Java 面试上积累了比较丰富的经验。其实,很多面试者在搜集面试资料的时候都踩...
  • (1)Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大对象的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为 2KB~128KB,可通过XX:...
  • 3 执行引擎包括即时编译器(JIT)和垃圾回收器GC,即时编译器用于将Java字节码编译成具体的机器码,垃圾回收器用于回收在运行过程中不再使用的对象。 本地接口库用于调用操作系统的native本地方法库完成具体的指令操...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,882
精华内容 752
关键字:

java面试核心知识点

java 订阅