java架构复习整理笔记

2PC 两阶段提交(XA事务,阻塞) 第一个阶段: 发出“准备”命令,全部事务参与者接受指令后进行资源准备,锁准备,undo log准备。若是都返回“准备成功”,若是不能执行,返回终止。 第二个阶段 协调者接受到第一个阶段的回复 若是都是ok,则发出“提交”命令,全部参与者进行commit操做。若是都成功,则事务结束,若是有失败状况,协调者发出“回滚”命令,全部事务参与者利用undo log进行回滚(这个在2PC不存在)。J2EE对JTA就是两阶段提交的实现。 若是有不ok,则发出撤销,全部事物撤销本地资源的锁定等操做mysql

TCC TCC是对二阶段的一个改进,try阶段经过预留资源的方式避免了同步阻塞资源的状况,可是TCC编程须要业务本身实现try,confirm,cancle方法,对业务入侵太大,实现起来也比较复杂。react

分布式事务的基本原理本质上都是两阶段提交协议(2PC),TCC (try-confirm-cancel)其实也是一种 2PC,只不过 TCC 规定了在服务层面实现的具体细节,即参与分布式事务的服务方和调用方至少要实现三个方法:try 方法、confirm 方法、cancel 方法。web

Saga Saga模式是现实中可行的方案,采用事务补偿机制。每一个本地事务都存储一个副本,若是出现失败,则利用补偿机制回滚。算法

最终一致性(BASE理论)spring

Paxossql

Raft数据库

mysql 索引:编程

  1. 普通索引
  2. 惟一索引
  3. 单列、多列索引
  4. 组合索引(最左前缀)

JVM : 垃圾回收机制顺序: 1.新生代 Serial (第一代) PraNew (第二代) Parallel Scavenge (第三代) G1收集器(第四代) 2.老年代 Serial Old (第一代) Parallel Old (第二代) CMS (第三代) G1收集器 (第四代)数组

G1以前的JVM内存模型 新生代:伊甸园区(eden space) + 2个幸存区 老年代:持久代(perm space):JDK1.8以前 元空间(metaspace):JDK1.8以后取代持久代缓存

配置参数:

G1 : G1模糊了内存分代概念,可是也保留了年轻代和老年代。因此G1没有Full GC。Fully young gc和Mixed gc. G1收集器收集范围是老年代和新生代。不须要结合其余收集器使用

CMS: CMS收集器是老年代的收集器,能够配合新生代的Serial和ParNew收集器一块儿使用

区别一: 使用范围不同 CMS收集器是老年代的收集器,能够配合新生代的Serial和ParNew收集器一块儿使用 G1收集器收集范围是老年代和新生代。不须要结合其余收集器使用

区别二: STW的时间 CMS收集器以最小的停顿时间为目标的收集器。 G1收集器可预测垃圾回收的停顿时间(创建可预测的停顿时间模型)

区别三: 垃圾碎片 CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片 G1收集器使用的是“标记-整理”算法,进行了空间整合,下降了内存空间碎片。

区别四: 垃圾回收的过程不同 CMS收集器                      G1收集器

  1. 初始标记                   1.初始标记

  2. 并发标记                   2. 并发标记

  3. 从新标记                   3. 最终标记

  4. 并发清楚                   4. 筛选回收

-----------------------------------------------io------------------------------------------------------

bio nio(同步非阻塞) aio(异步非阻塞)

bio: 同步阻塞的实现,代码以下能够看到代码和简单,这也是bio的一个优点,实现简单,可是性能是很低的,做为server端若是有大量连接进来那么,每一个read write都是阻塞的一个链接的请求处理完以前,下一个链接就必须等待 nio: aio: Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,所以在性能上没有明显的优点,并且被JDK封装了一层不容易深度优化。 Netty总体架构是reactor模型, 而AIO是proactor模型, 混合在一块儿会很是混乱,把AIO也改形成reactor模型看起来是把epoll绕个弯又绕回来。 AIO还有个缺点是接收数据须要预先分配缓存, 而不是NIO那种须要接收时才须要分配缓存, 因此对链接数量很是大但流量小的状况, 内存浪费不少。 Linux上AIO不够成熟,处理回调结果速度跟不上处理需求,好比外卖员太少,顾客太多,供不该求,形成处理速度有瓶颈(待验证)。

Java NIO知识都在这里 NIO的特性/NIO与IO区别:

1)IO是面向流的,NIO是面向缓冲区的; 2)IO流是阻塞的,NIO流是不阻塞的; 3)NIO有选择器,而IO没有。

读数据和写数据方式: 从通道进行数据读取 :建立一个缓冲区,而后请求通道读取数据。 从通道进行数据写入 :建立一个缓冲区,填充数据,并要求通道写入数据。 NIO核心组件简单介绍

Channels 一般来讲NIO中的全部IO都是从 Channel(通道) 开始的。 NIO Channel通道和流的区别:

Buffers Java NIO Buffers用于和NIO Channel交互。 咱们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels; Buffer本质上就是一块内存区; 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。

Selectors Selector 通常称 为选择器 ,固然你也能够翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此能够实现单线程管理多个channels,也就是能够管理多个网络连接。 使用Selector的好处在于: 使用更少的线程来就能够来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。

netty 源码其实就是用了nio设计,而且使用独有的reactor

粘包问题的解决策略 因为底层的TCP没法理解上层的业务数据,因此在底层是没法保证数据包不被拆分和重组的,这 个问题只能经过上层的应用协议栈设计来解决。业界的主流协议的解决方案,能够概括以下:

1.消息定长,报文大小固定长度,例如毎个报文的长度固定为200字节,若是不够空位补空格; 2.包尾添加特殊分隔符,例如毎条报文结朿都添加回车换行符(例如FTP协议)或者指定特殊 字符做为报文分隔符,接收方经过特殊分隔符切分报文区分; 3.将消息分为消息头和消息体,消息头中包含表示信患的总长度(或者消息体长度)的字段; 4.更复杂的自定义应用层协议。

优势:
减小线程切换的开销。
复用channel,能够选择池化channel EventLoopGroup EventLoop

zero copy的应用 减小并发下的竞态状况

首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。咱们以读操做为例来看看Reactor中的具体步骤:

读取操做:

  1. 应用程序注册读就需事件和相关联的事件处理器
  2. 事件分离器等待事件的发生
  3. 当发生读就需事件的时候,事件分离器调用第一步注册的事件处理器
  4. 事件处理器首先执行实际的读取操做,而后根据读取到的内容进行进一步的处理

下面咱们来看看Proactor模式中读取操做和写入操做的过程:

读取操做:

  1. 应用程序初始化一个异步读取操做,而后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
  2. 事件分离器等待读取操做完成事件
  3. 在事件分离器等待读取操做完成的时候,操做系统调用内核线程完成读取操做,并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序须要传递缓存区。
  4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不须要进行实际的读取操做。 Proactor中写入操做和读取操做,只不过感兴趣的事件是写入完成事件。

从上面能够看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操做是有谁来完成的,Reactor中须要应用程序本身读取或者写入数据,而Proactor模式中,应用程序不须要进行实际的读写过程,它只须要从缓存区读取或者写入便可,操做系统会读取缓存区或者写入缓存区到真正的IO设备.

综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步须要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。

--------------------spring------ aop ioc

Spring的AOP是基于动态代理实现的,Spring会在运行时动态建立代理类,代理类中引用被代理类,在被代理的方法执行先后进行一些神秘的操做。 BTrace基于ASM、Java Attach Api、Instruments开发,为用户提供了不少注解。依靠这些注解,咱们能够编写BTrace脚本(简单的Java代码)达到咱们想要的效果,而没必要深陷于ASM对字节码的操做中不可自拔。

----------------------------锁

synchronized\ReentrantLock\volatile\Atomic(cas)

synchronized : 基于Monitor实现,底层使用操做系统的mutex lock实现的。Class和Object都关联了一个Monitor。

①.同步实例方法,锁是当前实例对象 ②.同步类方法,锁是当前类对象 ③.同步代码块,锁是括号里面的对象

Monitor;每一个对象都拥有本身的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,若是没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态
复制代码

synchronized修饰代方法:使用ACC_SYNCHRONIZED标记符隐式的实现

synchronized修饰代码块:字节码里面 monitorenter(进去) 每个对象都有一个monitor,一个monitor只能被一个线程拥有。当一个线程执行到monitorenter指令时会尝试获取相应对象的monitor,获取规则以下:

若是monitor的进入数为0,则该线程能够进入monitor,并将monitor进入数设置为1,该线程即为monitor的拥有者。 若是当前线程已经拥有该monitor,只是从新进入,则进入monitor的进入数加1,因此synchronized关键字实现的锁是可重入的锁。 若是monitor已被其余线程拥有,则当前线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor。

monitorexit(出去) 只有拥有相应对象的monitor的线程才能执行monitorexit指令。每执行一次该指令monitor进入数减1,当进入数为0时当前线程释放monitor,此时其余阻塞的线程将能够尝试获取该monitor。

缺点: 它没法中断一个正在等候得到锁的线程; 也没法经过投票获得锁,若是不想等下去,也就无法获得锁

ReentrantLock\读写锁 更加精细,稍微公平、提供了更多选择(lock,try、try(time)、lockInterruptibly)

性能不一致:资源竞争激励的状况下,lock性能会比synchronize好,竞争不激励的状况下,synchronize比lock性能好。 锁机制不同:synchronize是在JVM层面实现的,系统会监控锁的释放与否。lock是代码实现的,须要手动释放,在finally块中释放。能够采用非阻塞的方式获取锁。 用法不同:synchronize能够用在代码块上,方法上。lock经过代码实现,有更精确的线程语义。


volatile: 在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。 volatile只保证可见性,不保证原子性

Atomic 原理: JDK经过CPU的cmpxchgl指令的支持,实现AtomicInteger的CAS操做的原子性

CAS :在Java并发应用中一般指CompareAndSwap或CompareAndSet,即比较并交换。

CAS是一个原子操做,它比较一个内存位置的值而且只有相等时修改这个内存位置的值为新的值,保证了新的值老是基于最新的信息计算的,若是有其余线程在这期间修改了这个值则CAS失败。CAS返回是否成功或者内存位置原来的值用于判断是否CAS成功。

有点: 竞争不大的时候系统开销小。

问题:

  1. ABA问题 CAS须要在操做值的时候检查下值有没有发生变化,若是没有发生变化则更新,可是若是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,可是实际上却变化了。这就是CAS的ABA问题。 常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。 目前在JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法做用是首先检查当前引用是否等于预期引用,而且当前标志是否等于预期标志,若是所有相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
  2. 循环时间长开销大 上面咱们说过若是CAS不成功,则会原地自旋,若是长时间自旋会给CPU带来很是大的执行开销。
  3. 只能保证一个共享变量的原子操做

-----包 ReenterLock Atomic ConcurrentMap Executors ThreadFactory

locks

-------concurrenthashmap---- jdk1.7 ConcurrentHashMap写操做只会锁一段(锁住Segment中全部元素),对不一样Segment元素的操做不会互相阻塞,而HashTable用的是synchronized,会锁住整个对象,至关于一个HashTable上的操做都是并行的,连get方法都会阻塞其余操做。 换个说法吧,一个HashTable只有一把锁,最多只有一个线程获取到锁。

jdk1.8

1)Node的val和next均为volatile型 2)tabAt和casTabAt对应的unsafe操做实现了volatile语义

改进一:取消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),能够改进性能。

------- classLaoader 有BootStrap , Ext , App和用户自定义加载器,加载类的时候采用双亲委托机制。特别须要注意的是:加载器ClassLoader的父子关系和继承关系无关,也和加载器是有哪一个加载器加载的无关,而是在建立加载器时指定的父加载器有关,便是由人工指定的。好比说要肯定加载器A的父加载器,仅仅是由建立对象A时传进去的父加载器决定,而无论A的类型是什么,也无论A是由哪一个加载器加载的。

1, 双亲委托机制。 2, 同一个加载器:类A引用到类B,则由类A的加载器去加载类B,保证引用到的类由同一个加载器加载。

ContextClassLoader: Class.forname() DriverManager是JDK的基础类由BootstrapClassLoader加载,DriverManager加载第三方实现的话,必定会加载BootstrapClassLoader里面。Java引入了ContextClassLoader。 ContextClassLoader是线程的一个属性,getter和setter方法

-----二叉树 红黑树: 红黑树的特性: (1)每一个节点或者是黑色,或者是红色。 (2)根节点是黑色。 (3)每一个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!] (4)若是一个节点是红色的,则它的子节点必须是黑色的。 (5)从一个节点到该节点的子孙节点的全部路径上包含相同数目的黑节点。 有点:

B-树的特性: 1.关键字集合分布在整颗树中;

2.任何一个关键字出现且只出如今一个结点中;

3.搜索有可能在非叶子结点结束;

4.其搜索性能等价于在关键字全集内作一次二分查找;

5.自动层次控制

B+树(高度低)

1.全部关键字都出如今叶子结点的链表中(稠密索引),且链表中的关键字刚好是有序的; 2.不可能在非叶子结点命中; 3.非叶子结点至关因而叶子结点的索引(稀疏索引),叶子结点至关因而存储关键字)数据的数据层; 4.更适合文件索引系统

----threadlocal

它是一个数据结构,有点像HashMap,能够保存"key : value"键值对,可是一个ThreadLocal只能保存一个,而且各个线程的数据互不干扰。

  1. 保存线程上下文信息,在任意须要的地方能够获取!!!
  2. 线程安全的,避免某些状况须要考虑线程安全必须同步带来的性能损失!!!

threadlocal并不能解决多线程共享变量的问题,同一个 threadlocal所包含的对象,在不一样的thread中有不一样的副本,互不干扰 用于存放线程上下文变量,方便同一线程对变量的先后屡次读取,如事务、数据库connection链接,在web编程中使用的更多 问题: 注意线程池场景使用threadlocal,由于实际变量值存放在了thread的threadlocalmap类型变量中,若是该值没有remove,也没有先set的话,可能会获得之前的旧值 问题: 注意线程池场景下的内存泄露,虽然threadlocal的get/set会清除key(key为threadlocal的弱引用,value是强引用,致使value不释放)为null的entry,可是最好remove。

key 使用强引用:这样会致使一个问题,引用的 ThreadLocal 的对象被回收了,可是 ThreadLocalMap 还持有 ThreadLocal 的强引用,若是没有手动删除,ThreadLocal 不会被回收,则会致使内存泄漏。 key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,因为 ThreadLocalMap 持有 ThreadLocal 的弱引用,即便没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除。 比较以上两种状况,咱们能够发现:因为 ThreadLocalMap 的生命周期跟 Thread 同样长,若是都没有手动删除对应 key,都会致使内存泄漏,可是使用弱引用能够多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。

相关文章
相关标签/搜索