
什么都不懂的小萌新
暂时未有相关通用技术能力~
阿里云技能认证
详细说明1 高质量分布式应用: 高性能 ,可伸缩,高可用,低延迟的要求.同时保证一致性,容错性,可恢复性和安全性 1: 可伸缩性: 垂直伸缩时降低锁竞争,水平伸缩时常用的分布式缓存,分布式文件系统等方法 2: 高可用: 均衡负载, 构建可容错,对资源使用有限制的系统 3: 实现SOA时,可参考的概念:SCA ,ESB 和业界的实现SCA ESB , 1: SCA: Service Component Architecture:2: ESB:核心思想是基于中间件来实现系统的交互.基于消息中间件所构建的此系统交互的中间场所称为总线,系统间交互数据采用统一的消息格式,由总线完成消息的转化,路由,发送到相应的目标应用,(1)标准的消息通信格式,ESB系统中要定义系统发送以及接收消息时的消息格式,以便各个系统保持同样的方式与总线通信(2) 消息路由,是指当总线接收到消息后,根据消息中的数据来决定需要调用的系统.更为复杂的情况时,还可以基于消息路由实现功能编排,即当某个功能需要有多个系统共同完成时,可以在总线上以流程的方式编排访问系统的顺序.例如某功能需要首先访问A系统,然后根据A系统返回的结果来决定访问B系统还是C系统 (3)支持多种的消息交互类型: 支持 请求/响应和发布/订阅等方式, 请求/响应方式会更加方便实现同步请求, 发布/订阅方式则以更加方便实现异步的消息广播. (4) 支持多种网络协议,总线要和多个系统进行交互,通常要支持多种网络协议,例如:HTTP/IP UDP/IP HTTP (5)支持多种数据格式并能进行互相转换,多个系统需要发送消息到总线,并由总线将消息转发,但各个系统消息格式可能不一致,此时需要总线支持数据转换. 4: classloader结构: 5:JVM采用了invokestatic(静态) ,invokevirtual (实例),invokeinterface(接口方法)和invokespecial(调用private方法和编译后的)四个指令来执行不同的方法调用 6: 对于方法的指令解释执行,执行方式为经典的FDX循环方式,即:获取下一条指令,解码并分派,然后执行,在实现FDX循环时有: switch-threading,token-threading,direct-threading,subroutine-threding,inline-threading 等多种方式. Sun JDK采用token-threading.也做了一些其它优化: 栈顶缓存,(将栈顶的值缓存在寄存器) (2)部分栈帧共享, (3)在解释执行特殊情况会直接执行机器指令: Math.sin Unsafe.compareAndSwapInt 7: 方法区: 存放了要加载类的信息(名称,修饰符等),类中的静态变量,类中定义为final类型的常量,类中的Field信息,类中的方法信息.当开发人员在程序中通过Class对象的getName isInterface等方法来获取信息时.这些数据都来自方法区域. 也是全局共享 8: 对象的分配: (堆 TLAB(Thread Local Allocator ByteBuf) 在eden区)-9 : 线程的状态:
spring 设置 devtools 1: complier -> automatical 2:ctrl+shift+alt+/ -> registry -> app.running 默认数据源 1: exclude={DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class} 2: 多数据源使用 @Primery @Order 使用spring aop模块 1: spring.aop.auto=true 2: spring.aop.proxy-target-class=false 性能测试框架: dropwizard metrics 作为打造APM原型的起点 1: dropwizard提供了几个注解: Timed Gauge Counted Metered ExceptionMetered 2: spring security 1: security.user.name = xxx security.user.password=xxx 2: 核心是 认证和授权 (p61)
微服务基础篇 1: service consumer -> Proxy Server ->Load Balance -> Service Discovery -> Target Service 2: Broser curl Other -> Zuul -> Ribbon -> Euraka -> Restful API zuul: 是边缘服务,用来提供动态路由,监控,授权,安全,调度等功能,将权限控制等一些业务逻辑抽离出来,单独放到Zuul里,使得服务组件更简单,具有更好的复用性. Zuul还负责将请求路由到服务上,起到了前台接待人员的作用,负责访客接待,当然也可以不使用zuul,这时请求直接与后台的服务交互,但是这样破坏了接口原则,相当于不要前台人员直接去公司里面找人 zuul是反向代理工具,类似的还有Nginx,区别: 正向代理 代理的是客户端,反向代理代理的是服务端正向代理: 服务端不知道客户端是谁.反向代理: 客户端不知道服务端是谁. Ribbon 维护了一个服务列表,如果实例注销,ribbon能够自行将其剔除,但是如果该服务实例重新启动或者增加实例,需要手工调用Ribbon的接口吧服务加进Ribbon的服务列表.这个工作还是交给euraka好了,所以一般二者结合使用. 客户端负载均衡: 具体来说是通过采取某种策略为客户端选择一个服务端,从而能够减少对服务端的压力,达到均衡负载的效果 Feign: 是一个组件也是一个webservice客户端,用户服务之间调用. 微服务之间的通信有同步和异步: 同步的方式有: Rest,SOAP,RPC ,其中 Rest是面向资源的,SAOP的面向方法的,RPC是面向接口的.Rest的主要特点: 每个资源都有一个ID,链接资源在一起,使用标准方法,资源多重描述,无状态通信, 优势: 易开发已维护,使用RPC的话,需要调用接口方法,代码上有渗透. AXIS,CXF都会根据WSDL文件生成客户端代码,调用时代码耦合很多第三方代码 Kafka: 来自LinkedIn之后成为Apache的一部分,Kafka本身是一个基于分布式系统,具有快速,可扩展,高性能,低耦合,高可靠性,数据不丢失等优点,相比于传统消息系统有:(1)基于分布式(2)可同时为发布和订阅提供高吞吐量(3)支持多订阅者,当失败时能自动平衡消费者(4)将消息持久化到磁盘,因此可用于批量消费,如ETL以及实时应用等 系统架构: Broker:Kafka集群包含一个或多个服务器,这些服务器被称为BrokerTopic: 集群上有很多Topic,每个topic代表一类消息,所有发布到集群上的消息都会进入到属于自己的那个topic中,等待被处理 Partition:在物理存储上,每个topic包含一个或多个patition,创建topic时刻指定partition的数量,每个partition对应于一个文件夹,该文件夹下存储该partition的数据和索引文件.Producer: 负责发布消息到Kafaka brokerConsumer: 消息消费者,负责从kafka broker读取消息进行处理.Consumer Group: 每个消费者都属于一个特定的Consumer Group,没有就是默认组. 1: spring aop 的实现: (1):对java字节码进行重新编译,将切面插入字节码的某些点和面上,可以是使用cglib库来实现 (2)制定类加载器,在类加载时对字节码进行补充,在字节码中插入切面,增加了除业务逻辑之外的功能,JVM 自身提供的Java Agent机制就是在加载类的字节码时,通过增加切面来实现AOP的 (3)JVM本身提供了动态代理组件,可以通过它实现任意对象的代理模式,在代理的过程中可以加入切面的逻辑,例如使用java提供的API Proxy.newistance()和InvocationHandler来实现. (4)AspectJ是实现AOP的专业框架和平台.通过它可以实现任意方式的字节码切面. 2: Web Service: 每个服务都是对等的,并且互相是解耦的,通过WSDL定义的WSDL定义的服务发现接口进行访问,并通过SOAP协议进行通信.SOAP协议通常是一种在 http/https通道上传输xml数据来实现的协议, 但是每个服务都要依赖中心化的web service目录来发现现存的服务. xml数据在高并发网络传输中带来性能影响,后来被JSON取代. 3: ESB: 企业服务总线,是用于设计和实现网络服务化交互和通信的软件模型,是SOA的另一种实现方式,主要用于企业信息化的集成服务场景中. Mule是企业服务总线的一个实现.ESB也适用于 事件处理,数据转换,消息和事件异步队列顺序处理,安全和异常处理,协议转换和保证通信服务的质量等场景. | 服务1 | 服务2 | 服务3 | 服务4_ 企业服务总线 __ 数据库数据源 消息队列 大数据 ERP 特点: (1)ESB服务没有中心化的服务节点,每个服务都是通过总线交互ESB的功能和职责:(1) 监控和控制服务之间的消息路由(2)控制可插拔的服务化的功能和版本(3)解析服务之间的交互和通信的内容和格式(4)通过组合服务,资源和消息处理器来统一编排业务需要的信息处理流程(5)使用冗余来提供服务的备份能力. Web service 的问题:(1) 依赖中心化的服务发现机制(2)使用SOAP通信协议,通常使用XML格式来序列化通信数据,XML格式的数据冗余太大(3)服务化管理和治理设施并不完善 ESB的问题:(1) 虽然是SOA的实现的一种方式,但是更多的是系统集成的便利性,(2) 过重的整体服务,内部依然复杂. 微服务的交互模式: (1) 读者容错模式(Tolerate Reader):指微服务化中服务提供者和消费者之间如何对接口的改变进行容错. 服务提供者对外的接口的数据格式的改变,增加和删除,都会导致服务的消费者不能正常工作,在实现过程中不推荐使用严格校验策略,而是推荐使用宽松的校验策略,即使服务消费者拿到的消息报文发生了改变,程序也只需尽最大努力提出需要的数据,同时忽略不可识别的数据.只有在完全不能识别接收到的消息或者无法通过识别的信息继续进行处理流程时,才能抛出异常. 建议: 在服务接口的定义中,参数可以使用枚举值,在返回值的DTO数据中禁止使用枚举值,(枚举改变,反序列化会出异常) (2) 消费者驱动契约模式:是用来定义服务化之间交互接口改变的最佳规则. (3)服务契约分为: 提供者契约,消费者契约以及消费者驱动契约,他从期望与约束的角度描述了服务提供者与消费者之间的联动关系, (4) 去数据共享模式:与SOA服务化对比,微服务是去ESB总线,去中心化以及分布式.而SOA还是以ESB为核心实现遗留系统的集成,以及基于web service为标准实现的通用的面向服务的架构,在微服务领域,微服务之间的交互通过定义良好的接口来实现,不允许使用共享数据来实现. 有些方案使用缓存或者数据库作为两个微服务之间的纽带,在业务处理过程中,为了简单,前一个服务将中间结果存入数据库或者缓存,下一个服务从缓存或数据库中拿出数据继续处理. 这种方案的缺点 使得微服务之间除了接口契约还存在数据存储契约 上游的数据格式发生变化,可能导致下游的处理逻辑问题 多服务共享一个资源服务,对资源服务的运维难以划清职责和界限 在做双机房独立部署时,需要考虑服务和资源的路由情况,跨机房的服务调用不能使用独立的资源部署模式,因此难以实现服务自治在微服务设计时,一定不要共享缓存和数据库等资源,也不要使用总线模式,服务之间的通信和交互只能依赖良好的接口,通常使用Restful 样式的API或者透明的RPC调用框架 (5) `微服务的分解和组合模式:通常是用领域的动词和名词来划分微服务的,例如:对于一个电商后台系统,可以分解为, 订单,商品,商品目录,库存,购物车,交易,支付,发票,物流等子系统. (6) 组合微服务: (1)服务代理模式: 他根据业务的需求选择调用后端的某个服务,在返回给使用端之前,代理可以对后端服务的输出进行加工,也可以直接把后端服务的返回结果返回给使用端. (2) 服务聚合模式:根据业务流程处理的需要,以一定的顺序调用依赖的多个微服务,对依赖的微服务返回数据进行组合,加工和转换,最后以一定的形式返回给使用方.这里被依赖的微服务都有自己的缓存和数据库,聚合服务本身可以有自己的数据存储,包括缓存和数据库等,也可以简单的聚合,不需要持久化任何数据. 聚合服务的好处: 三个独立子服务可以独立开发,变更和部署,聚合服务封装下层的业务处理服务,由三个独立的子服务完成数据持久化等工作.项目结构清晰,三个子结构可以复用. (3)服务串联模式:类似一个工作流,最前面的一个服务接受请求和响应使用方,再与后面的服务交互.通常服务串联之间的调用使用同步的RESTFul风格的远程调用实现,一个请求占用一个线程,优先使用聚合模式 (4) 服务分支模式: 是服务代理模式,服务聚合模式和服务串联模式相结合的产物.例如支付服务对应多种支付渠道, (5)服务异步消息模式: 前面所有的服务组合都是用同步的restful风格的同步调用实现,同步调用模式在调用过程中会阻塞线程,如果服务方一直没有返回,则消费方会一直阻塞,严重时会出现撑满县城次,出现雪崩效应. 建议在构建微服务架构系统时,通常会梳理核心系统的最小化服务集合,这些核心系统使用同步调用,而其他核心链路以外的服务可以使用异步消息队列异步化. (6)服务共享数据模式: 这其实是反模式,一下两者情况使用, 单元化架构:一些平台固态出于对性能较高的要求,所以采用微服务将服务分拆,但是性能反而下降,可以让不同的服务共享一些资源例如缓存或数据库,甚至可以将缓存和数据库在物理上与微服务部署在一个物理机中,最大限度的减少网络通信带来的性能损耗,这种称为:单元化架构.二是遗留的整体服务,耦合度较高,分拆会出现数据一致性分问题 微服务的容错模式 1: 舱壁隔离模式: (1)微服务容器分组,例如支付平台的微服务,将每个节点的服务池分为三组:准生产环境()内侧使用,灰度环境(跑一些普通商户的流量)和生产环境(大部分生产流量和vip商户流量). (2)线程池隔离:一般将同一类功能划分在一个微服务中,尽量避免微服务过细而导致成本增加...... (3)熔断模式:hystrix等等 (4) 限流模式:服务的容量和性能是有限的,限流机制一般会控制访问并发量.方法 (1)计数器:通过原子变量计算单位时间内的访问数量,如超出某个阈值,则拒绝后续的请求.下一秒要清零操作,可以使用单独一个线程每隔一秒清零. (2) 令牌筒:比较流行,他通过一个线程在单位时间内产生固定数量的令牌,然后把令牌放入队列,每次请求调用需要从筒中拿取一个令牌,拿到令牌才有资格执行请求调用,否则只能等待拿到令牌在执行或直接丢弃. (3) 信号量:在应用层使用信号量 (5): 失效转移模式:若在微服务中发生了熔断或限流,该如何处理被拒绝的请求呢? (1)采用快速失败策略,直接返回错误,让使用放知道错误后自行解决. (2) 是否有备份服务,如果有备份服务,则迅速切换到备份服务 (3) 失败的服务有可能是某台机器有问题,而不是所有机器有问题,这种情况下适合使用failover策略,采用重试的方法来解决,但是这种方法要求服务提供者的服务实现了蜜等性 库的分类: (1) 一方库: 本服务在JVM进程内依赖的Jar包 (2) 二方库: 在服务外通过网络通信或者RPC调用的服务的Jar包 (3) 三方库: 所依赖的其他公司或者组织提供的服务或者模块. Java 微服务项目的层级结构: 服务导出层(war)[web.xml,spring环境] -> 服务接口层(Jar)[业务接口,DTO,枚举类] -> 服务实现层(Jar)[业务实现类,外部服务包装类,DAO] 注意这里有一个反模式: 永远不要在本地事物中调用远程服务,如果远程服务出了问题,则会拖长事物,导致应用服务器占用太多的数据库链接,让服务器负载迅速攀升,或拖垮数据库, RPC框架的比较: 1: RMI: jdk1.4开始就内置了远程服务调用的技术栈,一个Java进程内的服务可以调用其他Java进程内的服务,使用JDK的序列化和反序列化协议.RMI是JEE规范中EJB远程调用的基础,然后RMI没有广泛应用,原因如下: (1): RMI采用JDK自带的序列化和反序列化协议,不能跨语言 (2):使用了底层的网络协议,不如基于文本的HTTP可读,也不如HTTP被广泛认知和应用 (3):开源框架的流行,严重消弱了JDK资深技术的流行程度 2: Hessian 和 Burlap: (1) Hessain:将对象序列化为语言无关的二进制协议,而Burlap将对象序列化成与语言无关的xml数据,数据是可读,两者都是与语言无关,可以在多种语言的服务中互相调用 (2)Hessian 和 Burlap都适合传输小的对象, 对较大,复杂的对象,无论是在序列化方式还是传输通道上都没有RMI有优势. (3)由于服务化架构中的大量服务调用都是大规模,高并发的短小请求,因此Hessian 和Burlap协议在服务化架构中得到了广泛使用. 3: Spring Http Invoker: 重用了JDK内置的对象序列化技术传输对象,这与RMI原理是一样的.但是他通过HTTP通道传输数据,在效率上低于RMI,并且使用了内置的JDK序列化机制,因此也是不能跨语言的. Spring cloud 的架构:
Objects中的属性: 1: Object.keys 和 Object.getOwnPropertyNames: 检索对象中所有的属性名,前一个方法返回所有可枚举的属性名,后一个方法不考虑属性的可枚举性一律返回, 但都不支持Symbol属性,ES6中增加 Object.getOwnPropertySymbols()方法来检索对象中的Symbol属性 2: //obj instanceof Array == Array[Symbol.hasInstance](obj) function specailNumber(){} Object.defineProperty(specailNumber,Symbol.hasInstance,{value:(v)=>(v instanceof Number) && (v >= 1 && v <= 100)}) var two = new Number(2) zero = new Number(0) print(two instanceof specailNumber) print(zero instanceof specailNumber) ------------------------------------------------------------------- let collection = { 0:"hello", 1:"world", length:2, [Symbol.isConcatSpreadable]:true }; let message = ["HI"].concat(collection); print(message) //[ 'HI', 'hello', 'world' ] 0 ES5 有5种类型, ES6有6种类型 1: ES5: 字符串型,数字型,布尔型,null,undefined 2 : ES6 + Symbol ,Symbol是原始值,因此调用new Symbol() 会报错. 但是可以 typeof symbol 3: Symbol的描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法时才可以读这个属性 4: 所有使用可计算属性名的地方,都可以使用Symbol.Object.defineProperty()方法和Object.defineProperties()方法 5: Symbol.for("uid"), 首先在全局Symbol注册表中搜索键为"uid"的Symbol是否存在,存在返回,不存在,创建新的Symbol并返回 let firstname = Symbol("firstname"); //可计算 let person = { [firstname]:"Nichols" }; //将属性设置为只读 Object.defineProperty(person,firstname,{writable:false}); let lastname = Symbol("last name"); Object.defineProperties(person,{ [lastname]:{ value:"Zakes", writable:false } }); print(person.firstname,person.lastname); 1 箭头函数 1: 没有this,super,arguments和new.target绑定,箭头函数中的 this,super,arguments和new.target这些值由外围最近一层非箭头函数决定, 2: 不能通过new关键字调用, 箭头函数没有[[]Construct]方法,所以不能作为构造函数,如果用了new就会抛出错误. 3: 没有原型,由于不能通过new构造,因而没有构造原型的需求,所以箭头函数不存在prototype这个属性 4: 不可以改变this的绑定,函数内部的this值不可以改变,在函数的生命周期内始终保持一致. 5: 不支持 arguments对象,箭头函数没有arguments绑定,所以你必须通过明明函数和不定参数这两种形式访问函数的参数. 6: 不支持重复的命名参数 无论严格参数还是非严格模式下,箭头函数都不支持重复的命名参数 7: 箭头函数同样也有一个name属性,这与其他函数相同. 8: 箭头函数的设计初衷是: 即用即弃, 箭头函数内的this也不能通过call apply bind来改变.但是可以在箭头函数上调用call apply bind方法,与其他函数不同,箭头函数的this值不会受这些方法的影响 var sum = (num1,num2) => num1+num2; print(sum.call(null,1,2)) print(sum.apply(null,[1,2])) var boundSum = sum.bind(null,1,2) print(boundSum()) 2 ES6中尾递归优化: 满足以下条件,尾调用不再创建新的栈帧,而是清除并重用当前栈帧 1: 尾调用不访问当前栈帧的变量(也就是说函数不是一个闭包) 2: 在函数内部,尾调用是最后一条语句 3: 尾调用的结果作为函数值返回 3: js中的对象 普通对象 特异对象 标准对象 内建对象 1: 普通对象(ordinary): 具有js对象所有的默认内部行为 2: 特异对象(exotic) 具有某些与默认行为不符的内部行为 3: 标准对象: ES6规范中定义的对象.Array Date等 4: 内建对象: 脚本开始执行时存在于js执行环境中的对象,所有标准对象都是内建对象. 4: Object.assign and 自有属性的枚举顺序 1: 所有数字按升序排序 2: 所有字符串键按照他们被加入对象的顺序排序 3: 所有symbol键按照他们被加入对象的顺序排序 var obj = { a:1, 0:1, c:1, 2:1, b:1, 1:1 }; obj.d = 1; print(Object.getOwnPropertyNames(obj).join("")) //012acbd 5: 改变对象的原型 Object.setPrototypeof() let person = { getGreeting(){ return "hello"; } } let dog = { getGreeting(){ return "woof!"; } } //person prototype let friend = Object.create(person); print(friend.getGreeting()); //hello print(Object.getPrototypeOf(friend) == person); //true //set prototypeOf dog Object.setPrototypeOf(friend,dog); print(friend.getGreeting()) //woof! print(Object.getPrototypeOf(friend) == dog); //true 6: 简化原型访问的super引用,必须在简写方法的对象中使用Super引用 let person = { getGreeting(){ return "hello"; } } let dog = { getGreeting(){ return "woof!"; } } let friend = { getGreeting(){ //return Object.getPrototypeOf(this).getGreeting.call(this) +" hi!"; return super.getGreeting() +" hi!"; } } Object.setPrototypeOf(friend,person); print(friend.getGreeting()) print(Object.getPrototypeOf(friend) === person) Object.setPrototypeOf(friend,dog); print(friend.getGreeting()) print(Object.getPrototypeOf(friend) == dog) 7: 使用var let const解构声明变量,则必须提供初始化程序. 1: 如果不使用解构功能,则var let声明不强求提供初始化程序,但是const需要 2: 任何解构表达式尝试读取 null or undefined的属性时都是报错. 3: 读取type的属性并将其存储在变量localType中,let {type:localType,name:localName} = node 4: 可以嵌套读取 let node = { type:"Identity", name:"foo", loc:{ start:{ line:1, column:1 }, end:{ line:2, column:2 } } } let {loc :{end}} = node ; print(end.line,end.column) let a = 1, b = 2 ; [a,b] = [b,a] print(a,b) //交换 a,b的值 // 复制数组 let colors = ["red","green","blue"]; let [...clone] = colors print(clone) print(colors.join(" ")) print(colors.concat(" ")) //应用场景1 function setCookie(name,value,{secure,path,domain,expire} = {}) //使用解构的话比较清晰 // ES6 之前的模拟 set and map var set = Object.create(null); set.foo = true if(set.foo){ print("hello") } var map = Object.create(null) map.foo = "bar"; print(map.foo) // 若引用set, 集合中的弱引用如果是对象的唯一引用,则会被回收并且释放相应内存(不支持原始值,只支持引用类型) let set = new WeakSet(), key = {}; set.add(key); print(set.has(key)); //true set.delete(key); print(set.has(key)); //false //注意weakset: 不可迭代即不可以使用for..in (2)不能不报露任何迭代器例如keys and values (3)不支持forEach (4) 不支持size属性 // set and map底层都是Object.is判断相等的 let map = new Map(); map.set("title","Understanding js"); map.set("year",2016) print(map.get("year")) print(map.get("title")) //默认初始化 let map = new Map([["title","Understanding js"],["year",2016]]); //迭代map (value,key,ownermap) => print ... map.forEach((v,k) => print(k+" -> "+v)) //使用weak map 存储私有数据 let Person = (function(){ let privateDate = new WeakMap(); function Person(name){ privateDate.set(this,{name:name}); } Person.prototype.getName = function(){ return privateDate.get(this).name; }; return Person; }()); var p1 = new Person("shen"); var p2 = new Person("yang"); print(p1.getName()) print(p2.getName()) //新的迭代器: for ..of 循环,展开运算符(...) ,异步编程也可以使用迭代器 //ES5中写迭代器的写法, ES6中增加了 一个生成器对象,他可以让创建迭代器对象变得简单 function createIter(items){ var i = 0; return { next : function(){ var done = (i>=items.length); var value = !done ? items[i++] : undefined; return {done:done,value:value}; } } } var iter = createIter([1,2,3]) print(iter.next()) print(iter.next()) print(iter.next()) print(iter.next()) //生成器是一种返回迭代器的函数,通过function*来表示,函数中会用到新的关键字yield function*createIter(){ yield 1; yield 2; yield 3; } function*createIter(items){ for(let i = 0;i<items.length;i++){ yield items[i]; } } var iter = createIter([1,2,3]) print(iter.next()) print(iter.next()) print(iter.next()) print(iter.next()) //生成器对象的方法, let o = { *createIter(items){ for(let i =0;i<items.length;i++){ yield items[i]; } } } let iter = o.createIter([1,2,3]) print(iter.next()) print(iter.next()) print(iter.next()) print(iter.next()) //由于生成器默认会为Symbol.iterator属性赋值,因此所有通过生成器创建的迭代器都是可迭代对象 //for-of 循环每执行一次都会调用可迭代对象的next方法 //访问默认迭代器 let values = [1,2,3] let iter = values[Symbol.iterator](); print(iter.next()) print(iter.next()) print(iter.next()) print(iter.next()) //检测对象是否可迭代 //默认开发者定义的对象都是不可迭代对象,但如果给Symbol.iterator属性添加一个生成器,者可以变成一个迭代对象 let collection = { items:[], *[Symbol.iterator](){ for(let item of this.items){ yield item; } } } collection.items.push(1); collection.items.push(2); collection.items.push(3); for(let x of collection){ print(x) } // 内建迭代器: entries() values() keys() let data = new Map([["title","understanding ES6"],["format","ebook"]]); for(let [key,value] of data){ print(key+" -> "+value); } //第二次调用next()方法传入的值为4,他会被赋值给变量first,函数继续执行 function *createIter(){ let first = yield 1; let second = yield first + 2; yield second + 3; } let iter = createIter(); print(iter.next()); print(iter.next(4)); print(iter.next(5)); print(iter.next()); //yield语句也可以抛出异常 function *createIter(){ let first = yield 1; let second; try{ second = yield first + 2; }catch(ex){ second = 6; } yield second +3; } let iter = createIter(); print(iter.next()) // { value: 1, done: false } print(iter.next(4)) // { value: 6, done: false } print(iter.throw(new Error("Boom!"))) //{ value: 9, done: false } print(iter.next()) //{ value: undefined, done: true } // yield 遇到 return 也会提前返回 // 在某些情况下,需要两个迭代器合二为一,这时可以创建一个生成器,再给yield语句添加一个星号,就可以将生成数据的过程委托给其他生成器. function *createNumber(){ yield 1; yield 2; } function *createColor(){ yield "red"; yield "green"; } function *createCombinIter(){ yield *createNumber(); yield *createColor(); yield true; } var iter = createCombinIter(); print(iter.next()) //{ value: 1, done: false } print(iter.next()) //{ value: 2, done: false } print(iter.next())//{ value: 'red', done: false } print(iter.next())//{ value: 'green', done: false } print(iter.next())//{ value: true, done: false } print(iter.next())//{ value: undefined, done: true } //简单任务执行器: 由于执行yield语句会暂停当前函数的执行过程并等待下一次调用next()方法,因此可以创建一个函数,在函数中调用生成器生成相应的迭代器,从而在不用回调函数的基础上实现异步调用next方法 function run(taskDef){ //创建一个无使用限制的迭代器 let task = taskDef(); let result = task.next(); //循环调用next函数 function step(){ //如果任务没完成,则继续完成 if(!result.done){ result = task.next() step() } } //开始迭代 step(); } run(function*(){ print("1..."); yield; print("2 ..."); yield; print("3 ... "); }); //任务执行器之间互相通信 function run(taskDef){ //创建一个无使用限制的迭代器 let task = taskDef(); let result = task.next(); //循环调用next函数 function step(){ //如果任务没完成,则继续完成 if(!result.done){ result = task.next(result.value) step() } } //开始迭代 step(); } run(function*(){ let value = yield 1; print("value: "+value); // 1 value = yield value + 3 print("value: "+value) // 4 //如果result.value是一个函数 function run(taskDef){ //创建一个无使用限制的迭代器 let task = taskDef(); let result = task.next(); //循环调用next函数 function step(){ //如果任务没完成,则继续完成 if(!result.done){ if(typeof result.value == "function"){ result.value(function(err,data){ if(err){ result = task.throw(err); return; } result = task.next(data); step(); }) }else{ result = task.next(result.value); step(); } } } //开始迭代 step(); } let fs = require("fs"); function readFile(filename){ return function(callback){ fs.readFile(filename,callback); }; } run(function*(){ let contents = yield readFile("data.txt"); print(String(contents)); print("Done"); });
0 编程范式 基本概念 1: 库和工具包是为程序员带来自由的,框架是为程序员带来约束的 ,框架的意义在于使设计者在特定领域的整体设计上不必重新发明轮子,库和工具包的意义在于使开发者脱离底层编码,专注特定问题和业务逻辑 2: 设计模式(design pattern)和架构(architecture)不是软件产品而是软件思想, 设计模式是软件的战术思想,架构是软件的战略决策 3: 5个重要的编程范式: 命令式 函数式 逻辑式 对象式 并发式 命令式: 一切行动听指挥, 世界观: 程序是由若干行动指令组成的有序列表,方法论: 用变量来存储数据,用语句来执行指令声明范式: 目标决定行动对象范式: 明主社会的编程法则并发范式: 合作与竞争 4 : 3种核心编程范式采用不同的机制: 命令式 : 自动机机制 通过设计指令完成从初试状态到最终状态的转变 函数式: 数学变换机制,通过设计函数完成从自变量到因变量的计算 逻辑式: 逻辑证明机制,通过逻辑推理完成从题设到结论的证明 5: OOP: 以数据为中心组织逻辑,将系统视为相互作用的对象集合,并利用继承与多态来增加可维护性,可扩展性和可重用性 ,这种思想也能应用到函数式 和 逻辑式,只不过对象的方法从命令式的过程分别换位函数式中的函数 和 逻辑式中的断言,大致来说,命令式,函数式和逻辑式互相平行,而OOP与他们正交 6: JavaScript是基于对象(Object Base) 而不是OO(Object-Oriented),基于对象有2种形式: 限制版OOP:即具备对象概念但不具备OOP一些其他概念,如继承和多态 基于原型的(prototype-base)或者说基于实例的(instance based) 而不是通常OOP是基于类的(class-based) 7: 以C语言为例: 信息隐藏可以使用static来实现,继承可以用合成(composition)来代替, 多态可以利用 函数指针来实现.只要设计合理,C语言也具有: 可维护性,可扩展性和可重用性 8: 如果把整个流程看作是一课倒长得的树,过程式编程自树根向下,逐渐分支,直到每片树叶,类似数学中的分析法,即知果索因的逆推法 OOP则从每片树叶开始,逐渐合并,直到树根,类似数学证明中的综合法,即执因索果的正推法 倘若树根是主函数,离树根越近,离用户需求也越近.如果使用过程式,采用逆推法,树干的改变容易导致树枝响应改变,相反OOP从树叶开始设计,抽象程度高,收到波及程度小,因此更容易维护 OOP以对象为基本模块单位,而对象是实现中具有事物和抽象概念的模拟,这使得编程设计更自然人性化, 例如: 牛.吃(草) 与 吃.(牛,草) 9: 过程式以函数为基本单位,oop以对象为基本单位. 区别: 函数是被动的实体,对象是主动的实体 ,过程式程序的世界是君主制,主函数是国王,其他函数是成名,等级分明.OO程序世界是民主制所有对象都是独立而平等的公民,有权保护自己的财产和隐私并向他人寻求服务,同时有义务为他人提供承诺的服务.公民之间通过信息交流来协作完成各种任务 , 封装使得公民拥有个体身份,需要对自己负责,继承使得公民拥有家庭生份,需要对家庭负责,多态使得公民拥有社会身份,需要对社会负责 10: 泛型编程: Generic Programing: 将算法与与其作用的数据分离,并将后者尽可能泛话,最大限度的实现算法重用. 这种泛话是基于模板(template)的参数多态(parametric polymorphism)相比于OOP基于继承(inheritance) 的子类多态(subtyping polymorphism),不仅普适性更强而且效率也更高. GP最著名的代表: STL(算法,容器和迭代),其后Javav C# D等语言所吸纳.此外一些函数式语言如Haskell Satand ML,Ocaml也支持CP, 但是他们实现方式不同: C++ 和D采用 类型模板(template) Java采用类型擦除(type eraser) C#采用类型具话(reification) 附录:算法是一系列可行的步骤,容器是数据的集合,是抽象化的数组,迭代器是算法与容器之间的接口,是抽象化的指针.算法串联数据,数据实例化算法 11: SoC(Separation of Concerns):即关注点分离 DRY:(Don't Repeat YourSelf) 即尽量减少代码重复 最主要就是: 抽象和分解 抽象和分解的原则: 单一化和正交话,每个模块职责明确单一,模块之间互相独立,即: 高内聚低耦合(high cohesion&low coupling) 12: AOP以切面为模块,描述的是横切关注点(cross-cutting concerns) 也是:程序的纵向主流执行方向横向正交的关注焦点 单个执行点称为接入点(join point),例如调用某个对象的方法的前后. 符合预先指定条件的接入点集合称为切入点(point cut). 再如所有以set为命名开头的方法. 每段绑定的代码称为一个建议(advice) 望文生义: 接入点是点,切入处是面,面由点组成,advice定义于切入点上,执行与接入点处,换言之,共享一段附加代码的接入点组成了一个切入点.切入点一般用条件表达式来描述,不仅有广泛性还有预见性------以后新增的代码如果满足切入点条件的接入点.advice中的代码便自动附上.这是AOP的威力所在也是麻烦所在 OOP只能沿着继承树的纵向方向重用,而AOP则弥补了OOP的不足,可以在横向方向上重用. 即: AOP不是OOP的分支也不是超越了OOP,而是OOP的一种补充----- 尽管AOP不局限于OOP语言AOP实现的关键是将Advice的代码嵌入到主体程序之中,术语称为:编织(weaving). ----- 将问题分解后再合成,问题才得以还原. 编织分2种: (1)静态编织: 通过修改源码或者字节码(bytecode)在编译期(compile-time),后编译期(post-complie)或加载期(load-time)嵌入代码,这里涉及到了元编程和生产式编程, (2) 动态编织: 通过代理(proxy)等技术在运行期(run-time)实现嵌入. 具体工具包括: AspectJ,AspectC++和一些框架, AspectWerkz,Spring,Jboss Aop等 13: 事件驱动,(有事我叫你,没事别烦我) 采用警觉式者主动去轮询(polling),行为取决于自身的观察判断,是流程驱动的,符合常规的流程驱动式编程(Flow-Driven Programming) . 采用托付者被动等通知(notification),行为取决于外来的突发事件,是事件驱动,符合时间驱动式编程(Event-Driven Programing,简称:EDP) 14: 事件:是程序中令人关注的信息状态上的变化.在基于事件驱动的系统中,事件包括内建事件与用户自定义事件.其中内建事件又分为底层事件和语义事件.此外:事件还有自然事件和合成事件. Callback: 指能作为参数传递的函数或代码,他允许底层模块调用高层模块,使 调用者与被调用者从代码上解耦.异步callback在传入后并不立即调用,使调用者与被调用者从时间上解耦. 控制反转一般通过callback来实现,其目的是降低模块之间依赖性 ,控制反转,依赖反转和依赖注入是近义词,他们的主题是控制与依赖,目的是解耦,方法是翻转,而实现这一切的关键是抽象接口(包括 函数指针,抽象类,接口,C++中的泛型函子和C#中的委托) 事件编程的3个步骤: 实现事件处理器,注册事件处理器,实现事件循环 观察者模式又名发布订阅模式,即时事件驱动式的简化,也是事件驱动式的核心思想,MVC架构是观察者模式在架构设计上的一个应用 15:主体是控制与依赖,目的是解耦,方法是翻转,而实现这一切的关键是抽象接口,(不叫回调函数的原因是它比较古老,多出现于过程式编程,抽象接口更现代,更OO的说法,另外: 回调强调的是行为方式------底层反调高层,而抽象接口强调的是实现方式------正是由于接口具有抽象性,底层才能在调用它时无需考虑及高层的具体细节,从而实现控制反转) 16: 同样的思想用在整体系统的结构设计上称为架构模式,用在局部模块细节上称为设计模式,用在引导编程实践上则称为编程范式 17: Duck类型的哲学: 名义不重要,重要的是能力 , 静态类型检查类似于 '疑罪从有'的有罪推定制, 动态类型检查类似'疑罪从无'的无罪推定制 鸭子类型是动态类型的一种风格,允许非继承性多态,即: 一个对象的类型可以由其接口集合来确定,不需要显示继承,有利于代码重用也可能滥用 18: 动态类型语言(dynamic typing language),正是类型检查发生在运行期(run-time)的语言,静态类型语言(static typing language)是类型检查发生在编译期(运行期之前,否则容易误解为静态类型语言一定是编译型语言(compiled language)) scala是静态类型语言确实动态语言,VB支持动态类型确实静态语言,极少数语言没有类型检查(untyped or typeless) 如汇编语言或Forth 动态类型语言不需要显示的变量声明(explicit declaration),一些静态类型语言有时也不需要,典型的如:ML Hashkell之类的函数式语言,编译器可以通过上下文进行'类型推断' ,另外C#3.0也开始支持局部变量的类型推断以及c++ 的auto 19: 动态类型与弱语言,常常混为一谈但是类型的动静与强弱是完全正交的两个概念.: 静态类型语言中有强类型的Java 也有弱类型的C ,动态类型语言中,有强类型的Smalltalk也有弱类型的JavaScript 通常弱类型语言(weakly-typed language)允许一种类型的值隐形转化为另一种类型.有时也叫强制转换(自动转化..例如1+'2' 在c里面是'3' 在js里面是'12'), 而强类型语言(strongly-typed language)着意贯彻类型控制,为保证数据的完整和代码的安全有效,一般不允许隐形类型转换.如果需要转换必须是显性,一般通过我们熟知的cast来完成. 20: 类型的动静以类型的绑定(binding)时间来划分,类型的强弱以类型的约束强度来划分 21: 脚本语言以语言的实际用途为标志,动态语言以语言的语法特征为标志, 动态语言 22: C族静态语言: C,C++,JAVA,C# D ,非C族静态语言: VB Delphi, 动态语言有5中: Perl PHP Python Ruby 和Javascript 23: Java 的目的是让一种语言在多种平台上运行,运行在JVM上, C#(.net)的目的是让多种语言在一种平台上运行,运行在 CLR(common language runtime) 24: 5种基本抽象: 过程抽象(赋予程序员自定义运算operation能力),数据抽象(赋予自定义类型(type)能力),迭代抽象(赋予自定义循环能力,STL),类型抽象(赋予自定义类族(type family))和多态抽象(自定义多态类型(polymorphic)) 25: 以接口为中心就是设计而言的,强调对象的行为,以及对象之间的交互,不关心底层实现细节,更多的属于OOD的范畴,以数据为中心是就实现而言的,更多的属于OOP范畴,例如抽象数据类型重在设计,数据结构重实现,关键是属性的数据表示(representation) OOP以数据为中心而非算法为中心 26: C++友元类(friend class) 更多的是出于语法形式考虑,常用于运算符重载(operator overloading),一个类与其友元类或友函数是 联合关系而不是主客关系,并且是单向授权关系,(更像是一个类分成几个部分,C#3.0之后也引入了partial关键字) -27: 接口继承不是为了代码重用,而是为了代码被重用,类是实现,类型是接口,无论是Java c++还是C#,宏观上是对象式,微观上还是过程式 28: UML用子类指向父类的箭头表示继承关系,以此表明子类是通过泛话来得到父类的,反之,我们也用特化(specialization)来表示从父类到子类的关系 29: 继承是多态的基础,多态是继承的目的, 鸭子类型是不依赖继承的多态 30: 具体类型是创建对象的模板,抽象类型是创建类型的模块,抽象数据类型的核心是数据抽象,而抽象类型的核心是多态抽象 抽象类型: 一个类即使没有一个抽象方法也可以被声明为抽象,一个没有任何成员的空接口或称标记接口同样属于抽象类型 31: 动态语言里面的mix-in: (1)抽象性和依赖性,本身没有独立存在的意义,必须融合主体类型才能发挥作用 (2)实用性和可重用性,不仅提供接口还提供部分实现(3)专一性和细粒度性:提供的接口职责明确而单一 (4) 可选性和边缘性:为主体类型提供非核心的辅助功能 32: 抽象类的2个特征: 必须继承和无法实例化,但他们并非本质,关键还是他们的目的--------为类型服务 33: 值语义的对象是独立的,引用语义的对象确实可以共享的 34: OOP中对象的三大特性: 状态(state),行为(behavior) 和 标识(identity) 35: `关联关系可理解为has-a的'有' 关系,如人与工作单位的双向关联----某人又某个工作单位, 聚合(aggregation)是一种强关联,强调整体与部分的关系,可以理解为'owns-a'的拥有关系,如某部门拥有某些成员, '合成(composition)是一种强聚合,可理解为contains-a的含有关系,比如某工作单位含有某些部门' 36: 依赖反转: 高层模块不应依赖底层模块,他们都应依赖抽象, 抽象不应依赖细节,细节应依赖抽象 37: DI强调依赖的来源 ------ 完全由外部提供,该依赖最好是抽象的,但并非首要要求,DIP则强调依赖的抽象性. 其次他们的应用范围不同: DI是更加具体的策略,一般用于类级别的模块,故为一种设计模式, DIP还可以用于类库,组件,架构层等大级别的模块,故为一种设计原则 38: 组件(component) ------ 组件也是一个抽象层,也可以看作是一个抽象数据类型(ADT),因为他们都是规范接口为客户提供一系列相关服务, 控制反转并无严格的定义,但可抽象为一种管理组件依赖的机制,以保证组件总能在合适的时候获得合适的依赖,显然,依赖注入是控制反转的一种实现方式, 广泛被spring使用, 此外,依赖查找(Dependency Lookup是另一种实现方式,广泛被EJB2.0容器所使用) 39: 抽象与规范 间接与分离 依赖于控制 接口与服务,每一对靠前具有代表性: 称为 抽象原则,间接原则,依赖原则和接口原则,(此处的接口不限于OOP中的interface和abstract class为代表的抽象类型,也可以指任何一个组件为外界提供的API) , 抽象与规范是根本,间接与分离是手段,依赖于控制是关键,接口与服务是核心 40: 依赖反转:(DIP) 可以作为间接原则的一个推论,即在高层模块与底层模块之间引入一个抽象层.该抽象层实际率属于高层模块,因此把高层模块依赖于底层的关系'反转'为底层依赖于高层的关系, DIP并不能真正消除依赖,但改善了系统的依赖体系,使之更容易经受需求和环境的变化 DIP倡导依赖抽象层,是因为抽象层更稳定,一次依赖反转原则可重述为 稳定依赖原则:模块应朝着文定的方向依赖 DIP与面向接口编程均提倡: 编程应依赖规范而非实现,依赖抽象而非细节 多态合成是在合成类与被合成类直接利用多态机制插入抽象层(接口),它优于实现继承(即合成重用原则CRP) DI(依赖注入)强调依赖是来源外部的,DIP则强调依赖是抽象的 41: ISP(接口隔离原则): 不应强迫客户依赖那些他们不用的方法,多个专用的接口比单纯一个总接口更好 , SRP提倡高类聚的类ISP提倡高类聚的接口,同时他们还有助于降低系统的耦合度 42: 保边原则(protected variations): 受保护的变化,也是PV原则, 找出预计的变化点或者不稳定点,分配其职责以便用稳定的接口来包装 保变原则提倡的是 变中求稳侧重可维护性, 开闭原则提倡的是稳中求变,侧重可维护性 迪米特法则(Law of Demeter, LOD)又称 最少知识原则也是不要和陌生人交谈, -----> 他要求一个对象的方法只能调用一下对象:该对象本身,即: this或者self,该方法参数,该方法内部创建的对象,该对象的直接组成对象,包括其属性及集合属性中的元素, 例如: role.getEmployee().getCity()明显不满足LoD原则 43: 对象池与缓存池: 前者重用的是可互换的(interchangeable)等价对象,后者重用的是不可互换的特定对象,常见对象池: 数据库链接,套接字链接(socket),线程等 ... 44: 创建者模式(7): 静态工厂模式, 工厂方法模式, 抽象工厂模式: 类似模板方法,不同的工厂去实现不同产品 建造者模式: 不需要知道细节,只要知道最终产品,按步骤进行创建,抽象工厂需要知道产品细节 原型模式(clone), 对象池模式 和 单利模式, 对象的复用特点: 创建者模式都是为了避免通过构造器来创建对象的底层操作,对创建对象的逻辑进行封装和抽象,以提高软件的灵活应变能力 45: 结构模式(structure pattern): 他关注的是如何吧类和对象组合成更大的结构,实质上就是一个利用继承层级的类结构和聚合层级的对象结构来构建更高层抽象的过程 1: 桥接模式: 信息的隐藏能将接口与实现从逻辑上分离,却不能从物理上分离,桥接模式克服了这一缺点,使得一个类能同时拥有多种实现,并且可以动态切换 2 : 适配器模式: (和桥接模式有点类似): 可以解决服务提供者与服务享受着之间接口不兼容的问题 - 3: 装饰着模式: 对一个对象增加或者修改某些职责或行为 , 适配器模式也称包装模式(wrapper pattern) 恰巧也是装饰着模式的别名 适配器模式改变对象的接口而保持对象的职责,装饰器模式改变对象的职责而保持对象的接口, 代理模式也一样保持对象的接口,不同的是代理模式通常增加接口服务,从行为上来看,Proxy对象在对客户的请求进行必要的处理后再转发给RealSubject对象,满足代理的实质条件 4: 代理模式: 如果摸个对象的初始化十分耗费时间或资源,却又未必立即投入使用,便可以使用代理来延迟创建(lazy initialization) 还有ORM框架中,加载一个与数据库对应的实体(entity)对象可能会返回一个代理对象,以减少对数据库的访问-----这里代理叫 虚拟代理(virtual proxy) 还有一种多用于分布式系统中的远程代理(remote proxy) :它可以把远程机上的某个对象用本机上的对象(stub)来表示 这两种的区别: (1) 虚拟代理: 对象是否被真正的创建,远程代理:对象是否在本地创建 .(3)由于远程对象与本地对象的地址空间(adress space)不同,Proxy对象不能直接引用RealSubject等(不过可以使用web serverice,RMI等技术实现) 5: 外观模式(facade pattern): 引入一个抽象的中间层,简化了外界对某个子系统的访问,优点(1)客户不再与各种不同的类打交道,之需与一个统一的接口进行通信,符合Lod原则(2)子系统中各服务类发生边界 6: `外观模式与 (代理模式和装饰器模式)的区别: 前者改变了被包装对象的接口,而后两者没有改变接口与(适配器模式)的区别: 他是为了提供一种更高层次的抽象和粒度更粗的服务,而适配器模式通常只是单纯的作接口转换` 7: 复合模式: 他和装饰模式一样,都是利用类型层次和聚合层次来构造更大的符合结构 8: 享元模式 的关键抽象出一类对象内在的,不因环境而异(context-insensitive)的状态,封装后作为共享单元----flyweight, 客户端要承担更多的责任-----存储或计算flyweight的上下文(context)信息,否则对象因数据不足而无法提供服务 9: 结构模式关注的是对象静态的结构,行为模式(behaiorial pattern)关注的是对象的动态行为: 分为: 责任链模式, 观察者模式, 中介者模式,状态模式, 备忘录模式,访问者模式,迭代器模式,解释器模式 10: 责任链模式: 消息发送者虽然知道'哪一类'对象是接受者,却不知道'哪一个'对象实际参与了请求的处理,例如: 事件模型(evnet-model),Servlet filter 和异常处理中(exception handling), ,由于责任链可以在运行期改变,他实际上实现了动态层次 1高质量分布式应用: 高性能 ,可伸缩,高可用,低延迟的要求.同时保证一致性,容错性,可恢复性和安全性 1: 可伸缩性: 垂直伸缩时降低锁竞争,水平伸缩时常用的分布式缓存,分布式文件系统等方法 2: 高可用: 均衡负载, 构建可容错,对资源使用有限制的系统 3: 实现SOA时,可参考的概念:SCA ,ESB 和业界的实现SCA ESB , 1: SCA: Service Component Architecture:2: ESB:核心思想是基于中间件来实现系统的交互.基于消息中间件所构建的此系统交互的中间场所称为总线,系统间交互数据采用统一的消息格式,由总线完成消息的转化,路由,发送到相应的目标应用,(1)标准的消息通信格式,ESB系统中要定义系统发送以及接收消息时的消息格式,以便各个系统保持同样的方式与总线通信(2) 消息路由,是指当总线接收到消息后,根据消息中的数据来决定需要调用的系统.更为复杂的情况时,还可以基于消息路由实现功能编排,即当某个功能需要有多个系统共同完成时,可以在总线上以流程的方式编排访问系统的顺序.例如某功能需要首先访问A系统,然后根据A系统返回的结果来决定访问B系统还是C系统 (3)支持多种的消息交互类型: 支持 请求/响应和发布/订阅等方式, 请求/响应方式会更加方便实现同步请求, 发布/订阅方式则以更加方便实现异步的消息广播. (4) 支持多种网络协议,总线要和多个系统进行交互,通常要支持多种网络协议,例如:HTTP/IP UDP/IP HTTP (5)支持多种数据格式并能进行互相转换,多个系统需要发送消息到总线,并由总线将消息转发,但各个系统消息格式可能不一致,此时需要总线支持数据转换. 4: classloader结构: 5:JVM采用了invokestatic(静态) ,invokevirtual (实例),invokeinterface(接口方法)和invokespecial(调用private方法和编译后的)四个指令来执行不同的方法调用 6: 对于方法的指令解释执行,执行方式为经典的FDX循环方式,即:获取下一条指令,解码并分派,然后执行,在实现FDX循环时有: switch-threading,token-threading,direct-threading,subroutine-threding,inline-threading 等多种方式. Sun JDK采用token-threading.也做了一些其它优化: 栈顶缓存,(将栈顶的值缓存在寄存器) (2)部分栈帧共享, (3)在解释执行特殊情况会直接执行机器指令: Math.sin Unsafe.compareAndSwapInt 7: 方法区: 存放了要加载类的信息(名称,修饰符等),类中的静态变量,类中定义为final类型的常量,类中的Field信息,类中的方法信息.当开发人员在程序中通过Class对象的getName isInterface等方法来获取信息时.这些数据都来自方法区域. 也是全局共享 8: 对象的分配: (堆 TLAB(Thread Local Allocator ByteBuf) 在eden区)-9 : 线程的状态:
五种IO模式 : * 五种IO模式 https://segmentfault.com/a/1190000003063859 Netty源码解析: http://www.jianshu.com/u/dbcfb30ec5e4 Netty 入门: http://www.jianshu.com/p/bcfde33fb644 同步与阻塞 : (1) 知乎上的讨论: https://www.zhihu.com/question/19732473 (2) http://blog.csdn.net/majianfei1023/article/details/45314803
Netty是一款用于快速开发高性能的网络应用程序的Java框架,他封装了网络编程的复杂性,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到 Netty不只是一个接口和一个类的集合,她还定义了一种架构模型以及一套丰富的设计模式 Netty的特点 分类 Netty的特性总结 设计 统一的API,支持多种传输类型,阻塞和非阻塞的,简单而强大的线程模型,真正的无连接数据报套接字支持,链接逻辑组件以支持复用 易于使用 详实的javadoc以及大量的实例 性能 拥有比Java核心API更高的吞吐量以及更低的延迟,得益于池化和复用,拥有更低的资源消耗,更少的内存复制 健壮性 不会因为慢速,快速或者超载的链接而导致OutOfMemory,消除在高速网络中NIO应用程序常见的不公平 读/写 比率 安全性 完整的SSL/TLS以及StartTLS支持,可用于受限环境,如Applet and OSGI 社区驱动 发布快而且频繁 Netty中的主要构件块: Channel callback Future event & ChannelHandler Channel: 可以把Channel看作是传入(入站)和传出(出站)数据的载体,因此他们可以被打开,关闭,链接或者断开链接. 回调: 其实就是一个方法,一个指向已经被提供给另外一个方法的方法引用.这使得后者(接受回调的方法)可以在适当的时侯调用前者. 例子: 当一个新的链接已经建立时,ChannelHandler的channerActive()回调方法将会被调用,并打印消息. public class ConnectHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client "+ctx.channel().remoteAddress()+" connected!"); } } Future : JDK 内置了 interface Future,但是其所提供的实现,只允许手动检查对应的操作是否已经完成或者一直阻塞直到他完成.非常繁琐. Netty提供了自己的实现ChannelFuture,用于在异步执行操作的时候使用. ChannelFuture提供了额外的方法,是的我们能够注册一个或多个ChannelFutureListener实例,监听回调方法operationComplete(),将会在对应的操作完成时被调用,然后监听器可以判断该操作是成功了还是出错了.如果是后者,我们可以检索产生的Throwable, 简而言之: 由ChannelFutureListener提供的通知机制消除了手动检查对应操作是否完成的必要. 每个Netty的出站IO操作都将返回一个ChannelFuture,也就是说他们都不会阻塞. Channel channel = null;//does not block ChannelFuture future = channel.connect(new InetSocketAddress("192,168.0.1", 25)); future.addListener(future1 -> { if(future.isSuccess()){ //如果操作是成功的,则创建一个ByteBuff以持有数据 ByteBuf buffer = Unpooled.copiedBuffer("Hello", Charset.defaultCharset()); //将数据异步地发送到远程节点,返回一个ChannelFuture ChannelFuture wf = future.channel().writeAndFlush(buffer); //... }else { Throwable cause = future.cause(); cause.printStackTrace();//如果发生错误,打印堆栈信息 } }); 事件和ChannelHandler: Netty使用不同的事件来通知我们状态的改变或者是操作的状态.这使得我们能够基于已经发生的事件来触发合适的动作: 包括 记录日志 数据转换 控制流 应用程序逻辑 Netty 是 一个网络框架,所以事件是按照他们入站和出站数据流的相关性进行分类的. 可能由入站数据或者相关的状态更改而触发的事件包括: 链接已被激活或者链接失活 数据读取 用户事件 错误事件, 出站事件是未来将会触发的摸个动作的操作结果, 包括: 打开或关闭到远程节点的链接 将数据写到或者冲刷到套接字. 每个事件都可以被分发给ChannelHandler类中的某个用户实现的方法. Netty的异步编程模型是建立在Future和回调的概念之上的,而将时间派发到ChannelHandler的方法则发生在更深的层次上. 结合在一起.这些元素就提供了一个处理环境,使你的应用程序逻辑可以独立于任何网络操作相关的顾虑而独立的改变.这也是netty的设计方式的一个关键目标.拦截操作以及高速的转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的Future. Netty通过触发事件将Selector从应用程序中抽象出来,消除了所有本来将需要手写的派发代码.在内部,将会为每个Channel分配一个EventLoop.用以处理所有时间.包括: (1)注册感兴趣事件(2)将事件派发给ChannelHandler(3)安排进一步动作. EventLoop本身只由一个线程驱动,其处理了一个Channel的所有IO事件,并且在该EventLoop的整个生命周期内不会改变 每个Channel都拥有一个与之相关联的ChannelPipeline,其持有一个ChannelHandler的实例链,在默认情况下,ChannelHandler会把对它的方法调用转发给链中的下一个ChannelHandler.因此,如果exceptionCaught方法没有被该链中的某处被发现,那么接收的异常将会被传递到ChannelPipeline的尾端并被记录,为此.你的应用程序应该提供至少一个实现了exceptionCaught方法的ChannelHandler //标示一个channelHandler可以被多个Channel安全的共享 @ChannelHandler.Sharable public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf)msg; System.out.println("Server received: "+in.toString(CharsetUtil.UTF_8)); ctx.write(in);//将接收到的消息写给发送者,而不冲刷出站消息 } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //将未决消息冲刷到远程节点,并且关闭该节点 ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace();//打印异常 ctx.close();//关闭该Channel } } ----------------------------------------------------------------------------------------------------------- public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws InterruptedException { if(args.length != 1){ System.err.println("Usage: "+EchoServer.class.getName()+" <port>"); } int port = Integer.parseInt(args[0]); new EchoServer(port).start(); } public void start() throws InterruptedException { final EchoServerHandler serverHandler = new EchoServerHandler(); EventLoopGroup group = new NioEventLoopGroup();// 1: 创建EventLoopGroup try { ServerBootstrap b = new ServerBootstrap();// 2: 创建ServerBootStrap b.group(group) .channel(NioServerSocketChannel.class) //3: 指定所使用的NIO传输Channel .localAddress(new InetSocketAddress(port)) // 4: 使用指定的端口设置套接字地址 .childHandler(new ChannelInitializer<SocketChannel>() { // 5: 添加一个EchoServerHandler到子Channel的ChannelPipeLine @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(serverHandler); } }); ChannelFuture future = b.bind().sync(); // 6: 异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成 future.channel().closeFuture().sync(); // 7: 获取Channel的CloseFuture,并且阻塞当前线程直到他完成 } finally { group.shutdownGracefully().sync();// 8: 关闭EventLoopGroup } } } 下面这些是服务器的主要代码组件 (1) EchoServerHandler实现了业务逻辑 (2) main方法引导了服务器,步骤如下 ,(1)创建一个ServerBootStrap的实现以引导和绑定服务器(2)创建并分配一个NioEventLoopGroup 实例进行事件的处理,如接受新连接以及读写数据(3)指定服务器绑定本地的InetSocketAddress (4)使用一个EchoServerHandler的实例化每一个新的Channel (5)调用ServerBootStrap.bind()方法绑定服务器. Echo客户端会: (1)连接到服务器(2)发送一个或多个消息(3)对于每个消息,等待并接受从服务器发回的相同的消息.(4)关闭连接 Channel --> Socket EventLoop --> 控制流,多线程处理,并发 ChannelFuture --> 异步通知 Channel接口: 基本的IO操作(bind(),connect read write) 依赖于底层网络传输所提供的原语.Cannel也是拥有许多预定义的专门化实现的广泛类层次结构的根, EmbeddedChannel LocalServerChannel NioSctpChannel NioSoketChannel EventLoop接口 :定义了Netty的抽象核心,用于处理链接生命周期中锁发生的事件. Channel EventLoop Thread EventLoopGroup 之间的关系: (1):一个EventLoopGroup 包含一个或多个EventLoop (2): 一个EventLoop在她的生命周期内只和一个Thread绑定 (3):所有的EventLoop处理IO事件都将在她专有的Thread上被处理 (4):一个Channel在他的生命周期内只注册一个EventLoop (5): 一个EventLoop可能会被分配一个或多个Channel 编写自定义ChannelHandler时经常遇到的适配器类: ChannelHandlerAdapter ChannelInBoundHandlerAdapter ChannelOutBoundHandlerAdapter ChannelDuplexHandler 编码器和解码器 : 入站消息会被解码,从字节码转换为另一种格式,通常是一个Java对象, 出站消息会被编码,他从当前格式被编码为字节 方向转换的原因: 网络数据总是一系列的字节 Netty里面为编码器和解码器提供了不同类型的抽象类 例如: ByteToMessageDecoder MessageToByteEncoder, 对于特殊的类型: 类似于: ProtobufEncoder and ProtobufDecoder 来预制用来支持Google 的Protocol Buffers. 所有的Netty提供的编码器和解码器适配类都实现了ChannelOutboundHandler and ChannelInboundHandler接口. 流经网络的数据总是有相同的类型: 字节 应用程序需求 推荐传输 非阻塞代码库或者一个常规的起点 NIO(或者linux上的Epoll) 阻塞代码库 OIO 在同一个JVM内部通信 Local 测试ChannelHandler的实现 Embedded(这个一般为自己的ChannelHandle)写单元测试. 网络数据的基本单位总是字节,java nio提供了ByteBuffer作为他的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐 Netty的数据容器 ByteBuf: Netty通过数据处理API通过2个组件暴露-- abstract class ByteBuf 和 interface ByteBufHolder ByteBuf的优点: (1)他可以被用户自定义的缓冲区类型扩展 (2)通过内置的符合缓冲区实现了透明的零拷贝 (3)容量可以按需增长(类似于StringBuilder) (4)在读和写之间切换不需要调用ByteBuffer的flip方法 (5)读和写使用了不同的索引 (6)支持方法的调用链 (7)支持引用计数 (8) 支持池化 堆缓冲区: 最常用的ByteBuf模式是将数据存储在JVM堆空间中.这种模式称为L支撑数组(backing array),他能在没有使用池化的情况下提供快速的分配和释放,代码如下: ByteBuf buf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); if(buf.hasArray()){ byte[] array = buf.array(); int offset = buf.arrayOffset()+buf.readerIndex();//计算第一个字节的偏移量 int length = buf.readableBytes(); System.out.println(offset+" - "+length); } 直接缓冲区: 是另外一种ByteBuf模式,通过本地内存来分配, 直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外,如果你的数据包含在一个堆上分配的缓冲区中,那么事实上,再通过套接字发送他之前,JVM将会在内部把你的缓冲区复制到一个直接缓冲区中. 直接缓冲区的缺点: :相对于堆的缓冲区,他们的分配和释放较为昂贵.如果处理遗留代码,因为数据不在堆上,所以不得不进行一次复制. 复合缓冲区: Netty通过一个ByteBuf的子类CompositeByteBuf---实现了这个模式,他提供了一个将多个缓冲区表示为单个合并缓冲区的虚拟表示,(堆内存+直接内存) CompositeByteBuf messagebuf = Unpooled.compositeBuffer(); ByteBuf headbuf = null; //can be backing or direct ByteBuf bodybuf = null;//can be backing or direct messagebuf.addComponents(headbuf,bodybuf); //do something messagebuf.removeComponent(0);//remove the header //iterate for (ByteBuf buf:messagebuf) { System.out.println(buf.toString(CharsetUtil.UTF_8)); } Netty定义了2个重要的ChannelHandler子接口: (1) ChannelInBoundHandler --- 处理入站数据以及各状态变化. (2) ChannelOutBoundHandler ---- 处理出站数据并且允许拦截所有的操作. public class DiscardHander extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ReferenceCountUtil.release(msg); // 显示的释放与池化ByteBuf实例相关的内存! } } 如果extends SimpleChannelInboundHandlerAdapter <Object> 则不需要显示的释放.自动释放 ChannelPipline (1)保存了与Channel相关联的ChanelHandler (2) 可以根据需要,通过添加或者删除ChannelHandler来动态修改(3)有着丰富的API用以被调用,以响应入站和出站事件. ChannelHandlerContext: (1)代表了ChannelHandler和ChannelPipeline之间的关联. (2)每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext. (3) 如果调用Channel或者ChannelPipline上的这些方法,他们将沿着整个ChannelPipeline进行传播.而调用ChannelHandlerContext上的相同的方法,则将从当前所关联的ChannelHandler开始,并且自会传播给位于该ChannelPipeline中下一个能够处理该事件的ChannelHandler 使用ChannelHandlerContext的注意点: (1) ChannelHandlerContext和ChannelHandler之间的关联是永远不会改变了,所以缓存对它的引用时安全的 (2)相对于其他类的同名方法,ChannelHandleContext的方法将产生更短的事件流,可以利用这个特性获得更大的性能提升. Channel PipeLine ChannlHandler ChannelHandlerContxet 之间的关系: 异常处理: (1)ChannelHandler.exceptionCaught() 的默认实现是简单的将当前异常转发给ChannelPipeLine中的下一个ChannelHandler (2)如果异常到达了ChannelPipeLine的最尾端,它将会被记录为未处理.(Netty将会通过WRAN级别的日志记录该异常到达了ChannelPipeLine的尾端,但没有被处理,并尝试释放该异常.) (3)要想自定义处理逻辑,需要重写exceptionCaught()方法,然后决定是否需要将该异常传播出去. 处理出站异常: (1)每个出站操作的都将返回一个ChannelFuture.祖册到ChannelFuture的ChannelFutureListener将在操作完成时被通知该操作是成功了还是出错了,,,(2)几乎所有的ChannelOutBoundHandler上的方法都会传入一个ChannelPromise的实例.作为ChannelFuture的子类,ChannelPromise也可以被分配用于异步通知的监听器,2种方法: ChannelFuture future = null;//some channe future.addListener((ChannelFutureListener)f -> { if(!f.isSuccess()){ f.cause().printStackTrace(); f.channel().close(); } }); public class OutBoundExceptionHandler extends ChannelOutboundHandlerAdapter{ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { promise.addListener((ChannelFutureListener) f -> { if(!f.isSuccess()){ f.cause().printStackTrace(); f.channel().close(); } }); } } 编解码器: 编码器:是将消息转换为合适于传输的格式(最有可能的就是字节) 解码器: 将网络字节流转换为应用程序的消息格式. 编码器操作出站数据,解码器操作入站数据 Netty所提供的解码器类 将字节解码为消息: ------ ByteToMessageDecoder and ReplayingDecoder 将一种消息类型解码为另外一种 ------ MessageToMessageDecoder /** * IdleStateHandler: 在链接空闲时间太长时,会触发一个IdleStateEvent 事件,然后通过userEventTriggered方法处理该事件 * ReadTimeOutHandler: 如果在指定的时间间隔内没有收到任何的入站数据,将抛出一个ReadTimeoutException并关闭对应的Channel,可以重写exceptionCaught() * 来检测该异常 * WriteTimeoutHandler: 如果在指定的时间间隔内没有收到任何的出站数据写入,抛出WriteTimeoutException并关闭对应的Channel */ public class IdleStateHandlerInitializer extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { //IdleStateHandler 将在被触发时发送一个IdleStateEvent 事件 ch.pipeline().addLast(new IdleStateHandler(0,0,60, TimeUnit.SECONDS), new HeartbeatHandler()); } private static final class HeartbeatHandler extends ChannelInboundHandlerAdapter{ private static final ByteBuf HEARTBEAT_SEQUENC = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("HEARTBEART", CharsetUtil.UTF_8)); @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if( evt instanceof IdleStateEvent){ //发送心跳消息并在发送失败时关闭该链接 ctx.writeAndFlush(HEARTBEAT_SEQUENC.duplicate()).addListener(ChannelFutureListener.CLOSE); }else { //如果不是IdleStateEvent 事件,所以将它传递给下一个InboundHandler super.userEventTriggered(ctx, evt); } } } } 解码基于分隔符的协议和基于长度的协议: DelimiterBasedFrameDecoder: 使用任何由用户提供的分隔符来提取帧的通用解码器 LineBasedFrameDecoder: 提取由尾行符(n或rn)分割的帧解码器,比上面的解码器要快. 基于长度的协议: FixedLengthFrameDecoder: 提取在构造函数时指定的定长帧 LengthFieldBasedFrameDecoder: 根据编码进帧头部中的长度值提取帧,该字段的偏移量以及长度在构造函数中指定. public class CmdHandlerInitializer extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new CmdDecoder(64*1024),new CmdHandler()); } private static final class Cmd{ private final ByteBuf name; private final ByteBuf args; public Cmd(ByteBuf name, ByteBuf args) { this.name = name; this.args = args; } public ByteBuf getName() { return name; } public ByteBuf getArgs() { return args; } @Override public String toString() { return name.toString(CharsetUtil.UTF_8)+" - "+args.toString(CharsetUtil.UTF_8); } } public static final class CmdDecoder extends LineBasedFrameDecoder{ public CmdDecoder(int maxLength) { super(maxLength); } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { ByteBuf frame = (ByteBuf)super.decode(ctx, buffer); if(frame == null) return null;//如果输入中没有帧 int index = frame.indexOf(frame.readerIndex(),frame.writerIndex(),(byte) ' '); return new Cmd(frame.slice(frame.readerIndex(),index),frame.slice(index+1,frame.writerIndex())); } } public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd>{ @Override protected void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception { System.out.println("get: "+msg); } } } 使用FileRegion接口的实现 在异步框架中高效的写大块的数据.例子显示了:如何通过从FileInputStream创建一个DefaultFileRgion.并将其写入channel.从而利用零拷贝特性来传输一个文件内容. Channel channel = null;//does not block File file = new File(""); FileInputStream in = new FileInputStream(file); DefaultFileRegion fileRegion = new DefaultFileRegion(in.getChannel(), 0, file.length()); channel.writeAndFlush(fileRegion).addListener((ChannelFutureListener) f ->{ if(!f.isSuccess()){ f.cause().printStackTrace(); } }); 这个实例只适合于文件内容的直接传输,不包括应用程序对数据的任何处理. 在需要将数据从文件系统复制到用户内存时,可以使用ChunkedWritedHandler,它支持异步写大型数据流,而不会导致大量的内存消耗 UDP: 无连接协议即用户数据报协议(UDP),它通常用在性能至关重要并且能够容忍一定的数据包丢失的情况下,最有名的基于UDP的协议就是 域名服务(DNS),其将完全限定的名称映射为数字的IP地址 `到目前为止都是一种叫 单播 的传输模式,定义为发送消息给一个由唯一地址所标识的单一网络目的地. 面向连接和无连接协议都支持这种模式. 但是UDP提供了向多个接受者发送消息的额外传输模式 (1)多播----传输到一个预定义的主机组 (2) 广播----传输到网络(或者子网)上的所有主机`
1: 并发编程基础 如何减少上下文切换: : 使用无锁并发编程 CAS算法 使用最少线程 使用协程使用无锁并发编程: 如将数据的ID按照HASH算法取摸分段,不同的线程处理不同的数据.(分段锁) CAS算法: Java的Atomic包使用的CAS算法来更新数据,而不需要加锁.使用最少线程: 避免创建不需要的线程.比如任务少的时候,但是线程数太多.使用协程 在单线程里实现多任务调度,并在单线程里维持多个任务的切换. 避免死锁的几个方法: (1)避免一个线程同时获取多个锁.(2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源.(3)尝试使用定时锁,使用lock.tryLock(timeout)来代替内部锁.(4)对于数据库锁,加锁和解锁必须在一个数据库链接里.否则会出现解锁失败的情况. volatile比synchronize轻量级.但只保证了可见性,没有保证原子性, 并且volatile不会引起线程的上下文切换和调度. 术语 英文单词 术语描述 内存屏障 memory barriers 是一组处理器指令,用于实现对内存操作的顺序限制 缓冲行 cache line 缓存中可以分配的最小存储单位.处理器填写缓存线时会加载整个缓存线,需要使用多个主内存读周期 原子操作 atomic operations 不可中断的一个或一系列操作 缓存行填充 cache line fill 当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个缓存行到合适的缓存(L1 L2 L3的或所有) 缓存命中 cache hit 如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理从缓存中读取而不是从内存读取 写命中 write hit 当处理器将操作数写回到一个内存缓存区时,他首先会检查这个缓存的内存地址是否在缓存中,如果存在一个高效的缓存行,则处理器将这个操作数写回到缓存而不是写回到内存 写缺失 write misses the cache 一个有效的缓存行被写入到不存在的内存区域 Java中每个对象都可以作为锁, 具体表现为三种形式 (1)对于普通方法,锁是当前的实例(2)对于静态方法,锁是当前类的Class对象(3)对于同步方法块,锁是Synchronize括号里配置的对象. JVM基于进入和退出Monitor对象来实现方法同步和代码同步, 代码同步: 使用monitorenter 和 monitorexit指令实现.而方法同步使用另外一种方式.细节在JVM规范里面没讲,但是,方法同步同样可以使用这两个指令来实现. monitorenter指令在编译后插入到同步代码块开始的位置,而monitorexit是插入到方法结束处和异常处 java1.6中 ,锁一共有四中状态: 无锁状态,偏向锁状态,轻量级锁状态和重量级状态 (依次从低到高) 偏向锁: ,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁. 当一个线程访问同步代码块并获取锁时,会在对象头和帧栈中记录里存储锁偏向的线程ID,以后该线程在进入和退出同步快时不需要进行CAS操作来加锁和解锁,只需要简单的测试一下对象头的MarkWord里是否存储着指向当前线程的偏向锁.如果测试成功.表示该线程已经获取锁.偏向锁使用了一种等到竞争出现才释放锁的机制,所以其他线程尝试竞争偏向锁时,持有偏向锁的线程才回释放, 轻量级锁: 线程在执行同步代码块之前,JVM会先在当前线程的帧栈中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中. 称为: Displaced Mark Word,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针.如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁. 锁 优点 缺点 使用场景 偏向锁 加锁和解锁不需要额外消耗,和执行非同步方法相比存在纳秒级差别 如果线程间存在锁竞争,会带来额外的锁撤销消耗 适用于只有一个线程访问同步块的场景 轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,会使用自旋消耗CPU 追求响应时间,同步快执行速度快 重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间慢 追求吞吐量,同步快执行速度长 CAS实现原子操作的三大问题: (1)ABA问题(2)循环时间长开销大(3)只能保证一个贡献变量的原子操作. ABA问题在java1.5之后提供了一个AtomicStampedReference来解决ABA问题.这个类的compareAndSet首先检查当前引用是否等于预期引用,并检查当前标志是否等于预期标志.如果全部相等,则以原子方式更新. 同步: 指程序中用于控制不同线程间操作发生相对顺序的机制. volatile:(1) 可见性, 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入(2)原子性: 对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性. 公平锁和非公平锁:(1)公平锁和非公平锁释放时最后都要写一个volatile变量state (2)公平锁获取时,首先去读volatile变量(state) (3) 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义 concurrent包的实现示意图: --------------------------------------------------------------- Lock | 同步器 | 阻塞队列 | Executor | 并发容器|--------------------------------------------------------------- AQS | 非阻塞数据结构 | 原子变量类 | --------------------------------------------------------------- volatile变量的读/写 CAS--------------------------------------------------------------- final域的内存语义: 对final域的读和写更像是普通变量的访问. 抛出InterruptedExeception的线程SleepThread. 其中断标识位被清除,而一直忙碌的线程BusyThread,中断标志位没有清除 public class Demo { public static void main(String[] args) { Thread sleepThread = new Thread(new SleepRunner(),"SleepThread"); sleepThread.setDaemon(true); Thread busyThread = new Thread(new BusyRunner(),"BusyThread"); busyThread.setDaemon(true); sleepThread.start(); busyThread.start(); sleep(5); sleepThread.interrupt(); busyThread.interrupt(); System.out.println("Sleep: "+sleepThread.isInterrupted()); System.out.println("Busy: "+busyThread.isInterrupted()); } static class SleepRunner implements Runnable{ @Override public void run() { while (true){ sleep(10); } } } static class BusyRunner implements Runnable{ @Override public void run() { while (true); } } } Exception in thread "SleepThread" java.lang.IllegalStateException: java.lang.InterruptedException: sleep interrupted at java9demo/com.java9.utils.ConcurrentUtils.sleep(ConcurrentUtils.java:34) at java9demo/com.java9.artcp.Demo$SleepRunner.run(Demo.java:30) at java.base/java.lang.Thread.run(Thread.java:844) Caused by: java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at java.base/java.lang.Thread.sleep(Thread.java:340) at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:401) at java9demo/com.java9.utils.ConcurrentUtils.sleep(ConcurrentUtils.java:32) ... 2 more Sleep: false Busy: true 过期的suspend resume stop: suspend: 在调用后,线程不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样容易引发死锁问题, stop方法会在终结一个线程时不会保证线程的资源正确释放,通常是没有给予线程完成资源释放工作的机会.因此会导致程序可能工作在不确定的状态下. static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); public static void main(String[] args) { Thread printThread = new Thread(new Runner(),"PrintThread"); printThread.setDaemon(true); printThread.start(); sleep(3); printThread.suspend(); System.out.println("main suspend at: "+LocalTime.now().format(formatter)); sleep(3); printThread.resume(); System.out.println("main resume at: "+LocalTime.now().format(formatter)); sleep(3); printThread.stop(); System.out.println("main stop at: "+LocalTime.now().format(formatter)); sleep(3); } static class Runner implements Runnable{ @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()+" Run at: "+ LocalTime.now().format(formatter)); sleep(1); } } } 终止线程: (1)中断方式,(2)利用一个boolean变量来控制是否需要停止任务并终止该线程. public static void main(String[] args) { Runner one = new Runner(); Thread countThread = new Thread(one,"CountThread"); countThread.start(); sleep(1); countThread.interrupt(); Runner two = new Runner(); countThread = new Thread(two,"CountThread"); countThread.start(); sleep(1); two.cancel(); } static class Runner implements Runnable{ private long i; private volatile boolean on = true; public void cancel(){on = false;} @Override public void run() { while (on && !Thread.currentThread().isInterrupted()){ i++; } System.out.println("count i= "+i); } } 对同步快的实现使用了monitorenter 和moniterexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZE来完成的. 无论采用哪种方式.其本质是对一个对象的监视器(moniter)进行获取,而这个获取是排他的.也就是同一时刻只能有一个线程获取到synchronize所保护的监视器 public static void main(String[] args) { synchronized (Demo.class){ } m(); } public static synchronized void m(){ } /------------------------------------------------------------------------------------------ public com.java9.artcp.Demo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/java9/artcp/Demo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // class com/java9/artcp/Demo 2: dup 3: astore_1 4: monitorenter 5: aload_1 6: monitorexit 7: goto 15 10: astore_2 11: aload_1 12: monitorexit 13: aload_2 14: athrow 15: invokestatic #3 // Method m:()V 18: return Exception table: from to target type 5 7 10 any 10 13 10 any LineNumberTable: line 6: 0 line 8: 5 line 9: 15 line 10: 18 LocalVariableTable: Start Length Slot Name Signature 0 19 0 args [Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 10 locals = [ class "[Ljava/lang/String;", class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 public static synchronized void m(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=0, locals=0, args_size=0 0: return LineNumberTable: line 13: 0 } SourceFile: "Demo.java" wait and notify的两种方式: 分为 等待方(消费者) 和通知方(生产者)等待方的原则: (1) 获取对象锁 (2)如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件(3)条件满足则执行对应的逻辑. 通知方(生产者): (1)获得对象锁(2)改变条件(3)通知所有等待在对象上的线程 `等待方(消费者)` synchronized (对象){ while(条件不满足){ 对象.wait(); } 对应的处理逻辑. } `通知方(生产者)` synchronized (对象){ 改变条件 对象.notifyAll(); } Thread.join():当前线程A等待thread线程终止之后才从thread.join返回.例子是: 创建了10个线程,编号0-9,每个线程调用前一个线程的join方法,也就是线程0结束了,线程1才能从join方法中返回,而线程0需要等待main线程结束. public static void main(String[] args) { Thread previous = Thread.currentThread(); for (int i = 0; i < 10; ++i) { Thread thread = new Thread(new Domion(previous), String.valueOf(i)); thread.start(); previous = thread; } } static class Domion implements Runnable{ private Thread thread; public Domion(Thread thread) { this.thread = thread; } @Override public void run() { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" terminate."); } } /---------------------------------------------------------------------------------- 0 terminate. 1 terminate. 2 terminate. 3 terminate. 4 terminate. 5 terminate. 6 terminate. 7 terminate. 8 terminate. 9 terminate. ThreadLocal的用法: 例如在AOP中,可以在方法调用前的切入点执行begin而在方法调用之后切入点执行end()方法. public class Profiler { private static final ThreadLocal<Long> TIME_THREADLOCAL = ThreadLocal.withInitial(System::currentTimeMillis); public static final void begin(){ TIME_THREADLOCAL.set(System.currentTimeMillis()); } public static final long end(){ return System.currentTimeMillis() - TIME_THREADLOCAL.get(); } public static void main(String[] args) { Profiler.begin(); sleep(1); System.out.println("Cost: "+Profiler.end()+" mills"); } } 一个简单的线程池实例 public interface ThreadPool<Job extends Runnable> { void execute(Job job); void shutdown(); void addWorkers(int num); void removeWorker(int num); int getJobSize(); } package com.java9.artcp; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> { //线程池最大限制数 private static final int MAX_WORKER_NUMBERS = 10; //线程池默认数量 private static final int DEFAULT_WORKER_NUMBERS = 5; //线程池最小数量 private static final int MIN_WORKER_NUMBERS = 5; //这是一个工作列表,回想里面插入工作. private final LinkedList<Job> jobs = new LinkedList<>(); //工作者列表 private final List<Worker> workers = Collections.synchronizedList(new ArrayList<>()); //工作者线程数量 private int workerNum = DEFAULT_WORKER_NUMBERS; //线程编号生成 private AtomicLong threadNum = new AtomicLong(); public DefaultThreadPool(){ initWorkers(DEFAULT_WORKER_NUMBERS); } public DefaultThreadPool(int num){ workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS : num < MIN_WORKER_NUMBERS ? MIN_WORKER_NUMBERS : num; initWorkers(num); } @Override public void execute(Job job) { if(job != null){ //添加一个工作,然后进行通知 synchronized (jobs){ jobs.add(job); jobs.notify(); } } } @Override public void shutdown() { workers.forEach(Worker::shutdown); } @Override public void addWorkers(int num) { synchronized (jobs){ //限制新增的worker数量不能超过最大值 if(num + this.workerNum > MAX_WORKER_NUMBERS){ num = MAX_WORKER_NUMBERS - this.workerNum; } initWorkers(num); this.workerNum += num; } } @Override public void removeWorker(int num) { synchronized (jobs){ if(num >= this.workerNum){ throw new IllegalArgumentException("beyond workNum"); } //按照给定的数量停止worker int count =0; while (count < num){ Worker worker = workers.get(count); if(workers.remove(worker)){ worker.shutdown(); count++; } } this.workerNum -= count; } } @Override public int getJobSize() { return jobs.size(); } //初始化线程工作者 private void initWorkers(int num){ for (int i = 0; i < num; ++i) { Worker worker = new Worker(); workers.add(worker); Thread thread = new Thread(worker,"ThreadPool-Worker-"+threadNum.incrementAndGet()); thread.start(); } } //工作者,负责消费任务 class Worker implements Runnable{ //是否工作 private volatile boolean running = true; public void shutdown(){running = false;} @Override public void run() { while (running){ Job job = null; synchronized (jobs){ //如果工作者列表为空,那么久wait while (jobs.isEmpty()){ try { jobs.wait(); } catch (InterruptedException e) { //感知到外部对workerThread的中断操作,返回 Thread.currentThread().interrupt(); return; } } //取出一个job job = jobs.removeFirst(); } if(job != null){ try { job.run(); } catch (Exception e) { //忽略运行中的异常 } } } } } } 同步队列器(AbstractQueuedSynchronizer) [AQS]: 是用来固件锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 主要使用的方式是继承 ,子类通过继承同步器来实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态的更改.这时就需要使用同步器提供的三个方法: (getState, setState(int newState)和 compareAndSetState(int expect,int update))来进行操作,因为他们能够保证状态的改变是安全的,子类推荐被定义为自定义组件的静态内部类,同步器自身没有实现任何同步接口,他仅仅定义了若干同步状态获取和释放的方法来供自定义同步组件使用 同步器既可以支持独占式获取同步状态,也可以支持共享式的获取同步状态 --> 实现的不同形式: ReentrantLock ReentrantReadWriteLock和CountDownLatch等. 同步器是实现锁的关键, 锁是面向使用者的,它自定义了使用者与锁交互的接口(比如可以允许两个线程并行访问).隐藏了实现细节 同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理,线程的排队,等待和唤醒等底层操作 方法名称 描述 boolean tryAcquire(int arg) 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后进行CAS设置同步状态 boolean tryRelase(int arg) 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 boolean int tryAcquireShared(int arg) 共享式获取同步状态,返回大于等于0的值,表示获取成功反之获取失败 boolean tryRelaseShared(int arg) 共享式释放同步状态 boolean isHeldExclusively() 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占 package com.java9.artcp; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class Mutex implements Lock { //静态内部类,自定义同步器 private static class Sync extends AbstractQueuedSynchronizer{ //是否处于独占状态 @Override protected boolean isHeldExclusively() { return getState() == 1; } //当状态为0的时候获取锁 @Override protected boolean tryAcquire(int acquires) { if(compareAndSetState(0,1)){ setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } //释放锁,将状态设置为0 @Override protected boolean tryRelease(int release) { if(getState() == 0) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } Condition newCondition(){return new ConditionObject();} } //仅需要将操作代理到Sync上即可 private final Sync sync = new Sync(); @Override public void lock() { sync.acquire(1); } @Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } @Override public boolean tryLock() { return sync.tryAcquire(1); } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1,unit.toNanos(time)); } @Override public void unlock() { sync.release(1); } @Override public Condition newCondition() { return sync.newCondition(); } } /--------------------------------------------------------------------------------------------------------- private static Mutex mutex = new Mutex(); private static Condition condi1 = mutex.newCondition(); private static Condition condi2 = mutex.newCondition(); private static Condition condi3 = mutex.newCondition(); private static volatile long count = 1; public static void main(String[] args) { Print1 print1 = new Print1(); Print2 print2 = new Print2(); Print3 print3 = new Print3(); print3.start(); print2.start(); print1.start(); } private static class Print1 extends Thread{ @Override public void run() { while (true){ mutex.lock(); try { while (count != 1){ condi1.await(); } System.out.println("111"); sleep(1000); count=2; condi2.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { mutex.unlock(); } } } } private static class Print2 extends Thread{ @Override public void run() { while (true){ mutex.lock(); try { while (count != 2){ condi2.await(); } System.out.println("222"); sleep(1000); count=3; condi3.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { mutex.unlock(); } } } } private static class Print3 extends Thread{ @Override public void run() { while (true){ mutex.lock(); try { while (count != 3){ condi3.await(); } System.out.println("333"); sleep(1000); count=1; condi1.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { mutex.unlock(); } } } } 在获取同步状态时,同步器维护了一个同步队列(FIFP),获取状态失败的线程都会被加入到队列中并在队列中进行自旋,移出队列(或停止自旋)的条件是前驱节点为头结点且成功获取了同步状态.在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态.然后唤醒头结点 的后继节点 设计一个同步工具,该工具在同一时刻,只允许至多两个线程同时访问,超过2个线程的访问将被阻塞 public class TwinsLock implements Lock { private static class Sync extends AbstractQueuedSynchronizer{ public Sync(int count){ if(count <= 0) throw new IllegalArgumentException("count must large than zero"); setState(count); } @Override protected int tryAcquireShared(int reduceCount) { for(;;){ int current = getState(); int newCount = current-reduceCount; if(newCount<0 || compareAndSetState(current,newCount)){ return newCount; } } } @Override protected boolean tryReleaseShared(int returnCount) { for(;;){ int current = getState(); int newCount = current+returnCount; if(compareAndSetState(current,newCount)){ return true; } } } Condition getCondition(){return new ConditionObject();} } private final Sync sync = new Sync(2); @Override public void lock() { sync.acquireShared(1); } @Override public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } @Override public boolean tryLock() { return sync.tryAcquireShared(1) > 0; } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1,unit.toNanos(time)); } @Override public void unlock() { sync.releaseShared(1); } @Override public Condition newCondition() { return sync.getCondition(); } } /------------------------------------------------------------------------------------------- private static TwinsLock lock = new TwinsLock(); public static void main(String[] args) { //开启10个线程 for (int i = 10; i < 100; ++i) { Worker worker=new Worker(); worker.setDaemon(true); worker.setName("thread-"+i); worker.start(); } //每隔一秒换行 for (int i = 0; i < 10; ++i) { sleep(1); System.out.println(); } } static class Worker extends Thread{ @Override public void run() { while (true){ lock.lock(); try { sleep(1000); System.out.println("--> "+Thread.currentThread().getName()); sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } } 阻塞队列(BlockingQueu): 方法/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出 插入方法 add(e) offer(2) put(e) offer(e,time,unit) 移除方法 remove(e) poll() take() poll(time,unit) 检查方法 element() peek() 不可用 不可用 ArrayBlockingQueue : 一个由数组组成的有界阻塞队列 LinkedBlockingQueue :一个由链表组成的有界阻塞队列,最大长度Integer.MAX_VALUE PriorityBlockingQueue : 支持优先级排序的无界阻塞队列 DelayQueue : 一个一个使用优先级队列实现的无界阻塞队列 SynchronousQueue : 一个不存储元素的阻塞队列 LinkedTransferQueue: 一个由链表构成的无界阻塞队列 LinkedBlockingDeque 一个由链表组成的双向阻塞队列 ForkJoin 框架 public static void main(String[] args) { ForkJoinPool forkJoinPool = new ForkJoinPool(); CountTask task = new CountTask(1,10000); ForkJoinTask<Integer> res = forkJoinPool.submit(task); try { System.out.println(res.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } static class CountTask extends RecursiveTask<Integer>{ private static final int THRESHOLD = 2000;//阈值 private int start,end; public CountTask(int start, int end) { this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; //如果任务足够小就计算任务 boolean canCompute = (end-start) <= THRESHOLD; if(canCompute){ for (int i=start;i<=end;i++){ sum += i; } }else { //如果任务大于阈值,就分裂成2个子任务 int middle = (start+end)/2; CountTask left = new CountTask(start,middle); CountTask right = new CountTask(middle+1,end); //执行子任务 left.fork();right.fork(); //等待子任务执行完,并得到其结果 int leftResult = left.join(); int rightResult = right.join(); //合并子任务 sum = leftResult + rightResult; } return sum; } } 原子类 :AtomicIntegerArray : 原子更新数组 , AtomicLongArray ,AtomicReferenceArray:原子更新引用类型数组里的元素.需要注意的是: 数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicInterArra对内部的数组元素进行修改时,不会影响传入的数组. static int[] value = new int[]{1,2}; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0,3); System.out.println(ai.get(0)); //3 System.out.println(value[0]); //1 } CyclicBarrier 的例子: public class CyclicBarrierDemo implements Runnable{ //创建4个屏障,处理完之后执行当前类的run方法 private CyclicBarrier c = new CyclicBarrier(4,this); private Executor executor = Executors.newFixedThreadPool(4); private ConcurrentHashMap<String,Integer> sheetBankWaterCount = new ConcurrentHashMap<>(); public void count(){ for (int i = 0; i < 4; ++i) { executor.execute(() -> { System.out.println("-> "); //计算当前的sheet的流水数据. sheetBankWaterCount.put(Thread.currentThread().getName(),1); //计算完插入一个屏障 try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); } } @Override public void run() { int result = sheetBankWaterCount.values().stream().reduce(0,Integer::sum); //输出结果 sheetBankWaterCount.put("result",result); System.out.println("result: "+result); } public static void main(String[] args) { CyclicBarrierDemo demo = new CyclicBarrierDemo(); demo.count(); } }
1: 并发的简短历史 程序在各自的进程中运行.互相分离,各自独立执行,由操作系统来分配资源.比如:内存 文件句柄 安全证书 ,如果需要相互通信的话, socket signal handlers shared memory semaphores 文件 线程安全: 一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为.,对于线程安全类的实例进行顺序或者并发的一些列操作都不会导致实例处于无效状态. synchronized (1)锁对象的引用 (2)锁保护的代码块. 每一个Java对象都可以隐式的扮演一个用于同步的锁的角色,这些内置的锁被称为内部锁或监视器锁,执行线程进入synchronized块之前会自动获得锁,而无论通过正常路径还是从快中抛出异常,线程都在放弃对synchronized块的控制时自动释放锁. 获得内部锁的唯一途径:进入这个内部锁保护的同步块或方法 Reentrancy: 当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞,然而内部锁是可重进入的,因此线程在试图获的它战友锁,请求会成功,重进入意味着请求是基于没线程(per-thread),而不是基于per-invocation的.重进入的实现是通过为每个锁关联一个请求计数器和一个占有它的线程,当计数器为0时,认为锁是未占有的,占有一个线程计数器+1 任务是逻辑上的工作单元,线程是使任务异步执行的机制 thread-per-task: 的一些缺点,(1)线程生命周期的开销(2)资源消耗量(3)稳定性,(应该限制可创建线程数目) 在Java类库中,任务执行的首要抽象不是Thread 而是Executor. 他为任务提交 和 任务执行之间解耦提供了标准的方法,为使用Runable描述任务提供了通用的方式. Executor的实现还提供了对生命周期的支持以及钩子函数,例如可以添加诸如统计收集,应用程序管理机制和监视器扩展. Executor 基于生产者-消费者模式.提交任务的执行者是生产者(生产待完成的工作单元).执行任务的线程是消费者(消费掉这些工作单元). 如果要在你的程序中实现一个生产者-消费者的设计,使用Executor通用是最简单的方式. 执行策略: 将任务的提交与任务的执行体进行解耦,它的价值在于让你可以简单的为一个类给定的任务制定执行策略,并保证以后的修改不至于太难. 任务在什么(what)线程中执行? 任务以什么(what)顺序执行(FIFO LIFO 优先级)? 可以有多少个(how many) 任务并发执行? 可以有多少个(how many)任务进入执行队列? 如果系统过载,需要放弃一个任务,应该挑选哪一个(which)任务? 另外,如何(how)通知应用程序知道这一切呢? 在一个任务的执行前和结束后,应该做什么(what)处理? 线程池 描述 newFixedThreadPool 创建一个定长的线程池,每提交一个任务就创建一个线程,直到池的最大长度(Int.Max,有性能问题的),如果一个线程由于非预期的Exception而结束,线程池会补充一个新的线程 newCachedThreadPool 创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,他可以灵活的回收空闲的线程.当需求增加时,可以灵活的增加线程 newSingleThreadExecutor 创建一个单一化的executor,他只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另外一个取代他,executor会保证任务依照任务队列所规定的顺序(FIFO,LIFO,优先级)执行. newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer 注意1 没有正确关闭Executor,将会阻止JVM结束 注意2 因为Executor是异步执行任务,提交的任务状态都不能立即可见,这些任务,有的可能已经完成,有的正在运行,其它的还可能在队列中等待执行.为了解决这些问题,ExecutorService扩展了Executor,添加了一些用于生命周期管理的方法 ExecutorService暗示了生命周期的三种状态: (1)运行(running) (2)关闭(shutting down) (3)终止(terminated) 线程池 描述 shutdown 开启了一个平缓的关闭过程:停止接受新的任务,同时等待已经提交的任务完成---包括尚未开始执行的任务 shutdownNow 开启了一个强制关闭的过程:尝试取消所有运行中的任务和排在队列中尚未开始的任务 注意1 在关闭后提交到ExecutorService中的任务,会被拒绝执行处理器(rejected execution handler)处理 Timer 对调度的支持是基于绝对时间,而不是相对时间的,由此任务对系统时钟的改变是敏感的,SchedualedThreadPoolExecutor只支持相对时间 (1)Timer只创建唯一的线程来执行所有的timer任务.如果一个timer任务的执行很耗时,会导致其他的TimerTask的时效性出现问题 (2)如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为.Timer线程并不捕获异常,所以TimerTask抛出的未检查的异常会终止timer线程.这种情况下,Timer也不会在重复恢复线程的执行了,它错误的认为Timer都被取消了.此时.已经安排但尚未执行的TimeTask也不会执行,新的任务也不能被调度,(这个问题叫: 线程泄漏) jdk5 之后就不推荐使用Timer类 static class Throwask extends TimerTask{ @Override public void run() { System.out.println(" -----------> "); throw new RuntimeException("hello world"); } } Timer timer = new Timer(); timer.schedule(new Throwask(),1); SECONDS.sleep(1); timer.schedule(new Throwask(),1); SECONDS.sleep(1); timer.schedule(new Throwask(),1); SECONDS.sleep(5); 如果需要构建自己的调度服务 仍然可以使用类库提供的DelayQueue 他是BlockQueue的实现,为SechduledThreadPoolExecutor提供了调度功能. DelayQueue管理着一个包含Delayed对象的容器,每个Delayed 都与一个延迟时间相关联:只有在元素过期后,Delayed才能让你执行take操作获取元素,从DelayQueue中返回的对象将依据他们所延迟的时间进行排序. Runnable 和 Callable 描述的都是抽象的计算型任务.这些任务通常是有限的:他们有一个明确的开始点,而且最终会结束.一个Executor执行的任务的生命周期有4个阶段: 创建 提交 开始 完成, 由于任务的执行可能会花费很长时间,我们希望可以取消一个任务. 在Executor框架中,总可以取消已经提交但尚未开始执行的任务,对已经开始的任务,只有他们响应中断,才可以取消 , 取消一个已完成的任务没有影响 Future 描述了任务的生命周期,并提供了相关的方法来获取任务的结果,取消任务以及检验任务是否已完成还是被取消. 大量的互相独立且同类的任务进行并发处理,会将程序的任务量分配到不同的任务中,这样才能真正的获得性能的提升. 如果你向Executor提交了一个批处理任务,并且希望在他们完成之后获得结果,为此你可以保存与每个任务相关联的Future,然后不断地调用timeout为0 的get来检验Future是否完成.这样做当然可以,但是太乏味. CompletionService整合了Executor和BlockingQueue的功能.你可以将Callable任务提交给它去执行.然后使用类似于队列中的take和poll方法.在结果完整可用时获得这个结果,像一个打包的Future.ExecutorCompletionService是实现CompletionService接口的一个类,并将计算任务委托给一个Executor. 任务取消 1: 立即停止会导致共享的数据结构处于一种不一致的状态,安全做法: 当要求他们停止时,他们首先会清除当前进程中的工作,然后再停止.这提供了很好的灵活性. 2: 任务取消: 当外部代码能够在活动自然完成之前,把它更改为完成状态,那么这个活动被称为可取消的 3: 取消原因: (1)用户请求取消(2)限时活动(timeout) (3) 应用程序事件 (4)错误(例如磁盘已满)(5) 关闭 4: 一个可取消的任务必须有取消策略(cancellation policy) ,这个策略详细说明关于取消的"how" "when" "what" ---- 其他代码如何请求取消该任务.任务在什么时候取消是否到达,响应取消请求的任务中应有的行为. static class PrimeGenarator implements Runnable{ private final List<BigInteger> primes = new ArrayList<>(); private volatile boolean cancelled; @Override public void run() { BigInteger p = BigInteger.ZERO; while (!cancelled){ p = p.nextProbablePrime(); synchronized (this){ primes.add(p); } } } public void cancel(){cancelled = true;} public synchronized List<BigInteger> get(){return new ArrayList<>(primes);} } PrimeGenarator primeGenarator = new PrimeGenarator(); new Thread(primeGenarator).start(); TimeUnit.SECONDS.sleep(1); primeGenarator.cancel(); System.out.println("get: "+primeGenarator.get().size()); 中断: PrimeGenarator 中的取消机制最终会导致寻找素数的任务退出,但不是立刻发生,而是需要花费一些时间.但是如果一个任务使用这个方案调用一个阻塞方法,比如BlockingQueue.put会有严重的问题.----- 任务可能永远都不能检查到取消标志.因此不会终结(例如生产者-消费者,生产者块,消费者慢,设置标志之后....生产者被阻塞了....检测不到取消标志) 线程中断是一个协作机制.一个线程给另外一个线程发送信号(singal)通知他在方便或者可能的情况下停止正在做的工作,去做其他的事情. 每个线程都有一个boolean类型的中断状态(interrupted status) ,在中断的时候状态被设置为true. interrupt方法中断目标线程,并且isInterrupted返回目标线程的中断状态 ,静态的interrupted 清除当前线程的中断状态,并返回它之前的值.这是清除中断状态的唯一方法 , 阻塞库函数,比如: Thread.sleep 和 Object.wait ,试图监视线程何时被中断,并提前返回.他们对中断的响应为:清除中断状态.抛出 InterriptedException. 这表现为阻塞操作因为中断的缘故而提前结束. JVM并没有对阻塞方法发现中断的速度做出保证.不过现实响应都很快 当前线程在并不处于阻塞的状态下发生中断,会设置线程的中断状态,然后一直等到被取消的活动获取中断状态,来检查是否发生了中断. ------ 如果不发生异常,中断状态会一直保存.直到有人特意去清除中断状态. 调用interript并不意味这必然停止目标线程正在进行的工作,它仅仅传递了请求中断的消息,线程会在下一个方便的时刻中断(这些时刻被称为取消点)..有一些方法对这样的请求很重视.例如:wait sleep 和join方法.当他们接到中断请求时会抛出一个异常.或者进入时中断状态已经被设置了.运行良好的方法能够完全忽略这样的请求,只要他们把中断请求置于合适的位置上.留给调用代码进行处理 静态的interrupted应该小心使用.因为他会清除并发线程的中断状态 守护线程和普通线程: JVM启动时所创建的所有的线程,除了主线程之外,其他的都是守护线程(比如垃圾回收和其他类似线程).当一个新的线程创建时.新线程继承了创建它的线程的后台状态.所以默认的情况下,任何主线程创建的线程都是普通线程. 二者差别 : 当一个线程退出时,JVM会检查一个运行中线程的详细清单,如果仅剩下守护线程.他会发起正常的退出.当JVM停止时,所有任然存在的守护线程都会被抛弃--------不会执行finally块.也不会释放栈-----JVM直接退出. 当任务都是同类.独立的时候.线程池才会有最佳的工作表现,如果将耗时的与短期的任务混合在一起.除非线程池很大,否则会有"赛车"的风险.如果提交的任务要依赖其他的任务,除非线程池是无限的.否则会有死锁的风险. 线程饥饿死锁(thread starvation deadlock) :只要池任务开始了无限期的阻塞,其目的就是等待一些资源或条件.此时只有另一个池任务活动才能使那些条件成立,比如等待返回值或者另一个任务的边界效应.除非你能保证这个池足够大,否则会发生线程饥饿死锁. 一个稳妥的资源管理策略是使用有限队列,比如ArrayBlockingQueue或者有限的LinkedBlockingQueue以及PriorityBlockingQueue. 如果队列满了以后: 有饱和策略(saturation policie) 对于一个有界队列,队列的长度与池的长度必须一起调节.一个大队列加一个小池.可以控制对内存和CPU的使用.还可以减少上下文切换.但是要接受潜在吞吐量约束的开销 对于庞大或无限的池.可以使用SynchronousQueue,完全绕开队列.将任务直接从生产者移交给工作者线程. SynchronousQueue并不是一个真正的队列.而是一种直接管理在线程移交信息的机制. 饱和策略: AbortPolicy(默认) CallerRunsPolicy DiscardPolicy DiscardOldestPolicy ,默认是AbortPolicy,但是会引起execute抛出未检查的RejectedExecutionException.调用者可以捕获这个异常.然后编写满足自己需求的代码处理. 遗弃(discard)策略会默认放弃这个任务. 遗弃-最旧的(discard-oldest) 策略选择丢弃的任务,是本应该接下来就执行的任务,该策略还会尝试去重新提交新任务.(如果工作队列是优先队列,那么遗弃最旧的策略选择丢弃的刚好是优先级最高的元素.所以混合使用遗弃最旧的饱和策略和优先级队列是不可行的) 调用者运行(caller-run)策略的实现形式.即不会丢弃哪个任务也不会抛出任何异常.他会把一些任务堆到调用者那里,以此减缓新任务流.他不会在线程池中执行最新提交的任务,但是他会在一个调用了execute的线程中执行, 当线程A占有锁L时,想要锁M, 但同时,线程B占有锁M,并尝试获得锁L.这两个线程将永远等待下去.(致命拥抱deadly embrace) 数据库系统设计就针对了监测死锁,以及从死锁中恢复. 一个事物(transaction)可能需要取的很多锁,并可能一直持有这些锁,直到所有事物提交.如此说来2个事物非常可能发生死锁,但这却不常见.数据库服务器监测到一个事物发生了死锁(is-waiting-for)...他会选择一个牺牲掉,使他推出事物. JVM在解决死锁问题方面有很大差距 锁顺序死锁: //简单的锁顺序死锁(不要这样做) static class LeftRightDeadLock{ private final Object left = new Object(); private final Object right = new Object(); public void leftRight(){ synchronized (left){ synchronized (right){ //dosomething() } } } public void rightLeft(){ synchronized (right){ synchronized (left){ //dosomething() } } } } //动态加锁顺序死锁(不要这样做) public void transferMoney(Account fromAccount,Account toAccount,DollarAmount amount){ synchronized (fromAccount){ synchronized (toAccount){ if(...) else (...) } } } 获得锁时..查看是否有嵌套...[不能有嵌套] 饥饿: 当线程访问他所需要的资源时却被永久拒绝.以至于不能再继续运行.这样就发生了饥饿,原因:(1)最常见的引发饥饿的资源的CPU周期 ,例如 线程优先级不当 在锁中执行无终止的构建(无线循环或者无尽等待资源) 弱响应性: CPU密集型的后台任务任然可能会影响响应性,因为他们会与事件线程共同竞争CPU的微周期.还有 不良的锁管理也可能引起弱响应性 例如一个线程长时间占有一个锁(可能对一个大容器进行迭代,并对每一个元素进行耗时操作) 活锁:尽管没有阻塞,线程任然不能继续,因为他不断重试相同的操作却总是失败.活锁通常发生在消息处理的应用程序中,如果消息处理失败的话.其中处理传递消息的底层框架会回退整个事物,并把它置回队首.如果消息处理程序对某种特定类型的消息处理存在bug,每次处理都会失败,那么每一次这个消息都会被从队列中取出,传递到存在问题的处理器(handler),然后发生事物回退. 反复相同操作-----这就是 毒药信息(poison message) 问题,虽然线程没有阻塞,但是不会前进 解决活锁的一种方案就是对重试机制引入一些随机性 多线程的开销: 与线程协调相关开销(加锁,信号,内存同步) ,增加的上下文切换,线程的创建和消亡,另一方面.一个没能经过良好的并发设计的应用程序会比相同功能的顺序程序性能更差. 应用程序的衡量: 服务时间,等待时间(衡量有多快), 吞吐量,生产量.,(用来衡量有多少,即限定计算资源的情况下,能够完成多少工作) 效率 可伸缩性 :当增加计算资源的时候,吞吐量和生产量能够相应的得以改进. ConcurrentLinkedQeque 和 Collections.synchronizedList(new LinkedList<>()): 同步的LinkedList用一个锁守护者整个队列的状态,在offer和remove调用时都要获取这个锁. ConcurrentLinkedQeque使用了非常巧妙的非阻塞队列算法,它使用了原子引用来更新各个链接指针.这两个.其中一个是把整个的插入和删除都实现为串行化的,而另一个则是把每个指针的更新变化变成串行的. synchronize volatile 提供的|可见性保证要求使用一个特殊的,名为存储关卡(memory barrier)的指令,来刷新缓存,使缓存无效,刷新硬件的写缓冲,并延迟执行的传递.同时也抑制了其他编译器的优化. 还有在存储关卡中,大多数操作是不能被重排的. 减少锁的竞争: (1)减少持有锁的时间(2)减少请求锁的频率(3)或者利用协调机制取代独占锁,从而允许更强大的并发性. ReentrantLock 和 synchronized(内部锁)有着相同的语义.内部锁的缺点: 不能中断那些正在等待获取锁的线程,并且在请求锁失败的情况下,必须无线等待.内部锁必须在获取他们的代码中被释放.使用方法(千万及得要在finally里释放锁): Lock lock = new ReentrantLock(); lock.lock(); try { //.... } finally { lock.unlock(); } 可定时的与可轮训的锁获取模式,是由tryLock方法实现,与无条件的锁获取相比,它具有更完善的错误恢复机制,在内部锁中,死锁是致命的-------唯一恢复的方法是重启程序.唯一的预防方法是构建程序时不要出错 static class ReadWriteMap<K,V>{ private final Map<K,V> map; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock r = lock.readLock(); private final Lock w = lock.writeLock(); public ReadWriteMap(Map<K, V> map) { this.map = map; } public V put(K k,V v){ w.lock(); try { return map.put(k,v); } finally { w.unlock(); } } public V get(K k){ r.lock(); try { return map.get(k); } finally { r.unlock(); } } } 每个java对象都能当做锁一样,每个对象也能当做条件队列.Object中的wait notify notifyAll 构成了内部条件队列的API,为了能够调用对象X的中的任一个条件队列方法.你必须持有对象X的锁 这是因为: 等待基于状态的条件机制必须和维护状态一致性机制紧密联系在一起, 除非你能检查状态,否则你不能等待条件.同时.除非你能改变状态,否则你不能从条件等待队列中释放其他线程 Object.wait 会自动释放锁.并请求OS挂起当前线程,让其他线程获得该锁进而修改对象的状态.当他唤醒时,他在返回前重新获得锁 就像内置锁和条件队列一样,当使用显示的Lock和Condition时,也必须要满足 **锁,条件谓词,和条件变量之间的三者关系** ,涉及条件谓词的变量必须由Lock保护.检查条件谓词时以及调用await和signal时,必须持有Lock对象 public class ConditionBoundBuffer<T> { protected final Lock lock = new ReentrantLock(); //condition: count < items.length private final Condition notFull = lock.newCondition(); //condition: count > 0 private final Condition notEmpty = lock.newCondition(); private int tail,head,count; private final T[] items ; public ConditionBoundBuffer(int size){ items = (T[])new Object[size]; } //阻塞,直到 notFull public void put(T t) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[tail] = t; if(++tail == items.length) tail=0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } //阻塞 直到notEmpty public T take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); T x = items[head]; items[head] = null; if(++head == items.length) head=0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } 使用Lock实现一个Semaphore public class SemaphoreOnLock { private final Lock lock = new ReentrantLock(); //条件谓词: permitsAvailable (permits > 0) private final Condition permitsAvalable = lock.newCondition(); private int permits; public SemaphoreOnLock(int initPermits){ lock.lock(); try { permits = initPermits; } finally { lock.unlock(); } } //阻塞: 直到 permitsAvailable public void acquire() throws InterruptedException { lock.lock(); try { while (permits <= 0) permitsAvalable.await(); --permits; } finally { lock.unlock(); } } public void release(){ lock.lock(); try { ++permits; permitsAvalable.signal(); } finally { lock.unlock(); } } } AQS Synchronize 子类会根据 acquire 和release的语义,使用getState setState以及CompareAnSetState来检查并更新状态.然后通过返回值的状态值告知基类这次获取或释放的尝试是否成功. 例如: tryAcquireShared返回一个负值,说明该操作失败. 返回零说明Synchronize是被独占获取的. 返回正值说明Synchronizer是被非独占获取的.对于tryRelease和tryReleaseShared方法来说.如果能释放一些正在尝试获取Synchronizer的线程.解除这些线程的阻塞,那么这两个方法将返回true public class OneShotLatch { private final Sync sync = new Sync(); public void signal(){sync.tryReleaseShared(0);} public void await() throws InterruptedException {sync.acquireSharedInterruptibly(0);} private class Sync extends AbstractQueuedSynchronizer{ @Override protected int tryAcquireShared(int arg) { //如果闭锁打开成功(state == 1) 否则失败 return (getState() == 1) ? 1 : -1; } @Override protected boolean tryReleaseShared(int arg) { setState(1); //闭锁现在已经打开 return true; //现在其他线程可以获得闭锁 } } } volatile变量与锁相比是更轻量级的同步机制.因为他们不会引起上下文的切换和线程调度.然而volatile变量与锁相比有一些局限性: 只提供了内存可见性,没有保障原子性,加锁的另外一个缺点: 当一个线程正在等待锁时,他不能做其他任何事情,如果一个线程在持有锁的情况下发生了延迟(包括页错误,调度延迟或者类似情况),那么其它所有需要该锁的线程都不能前进了 CAS有三个操作数: 内存位置V,旧的预期值A和新值B, 当且仅当V符合旧值A时,CAS用新值B原子化更新V的值,否则他什么都不做.任何一种情况下都会返回V的真实值 , CAS的意思是: "我认为V的值是A,如果是,那么将其赋值为B,若不是,则不修改.并告诉我应该为多少 " ,CAS是一种乐观技术, 当多个线程试图使用CAS同时更新相同的变量时,其中一个会胜处,并更新变量的值,其他的都会失败.失败的线程不会被挂起(但是没有获取锁的线程就会被挂起) public class SimulateCAS { private int value; public synchronized int get(){return value;} public synchronized int compareAnsSwap(int expectedValue,int newValue){ int oldValue = value; if(oldValue == expectedValue) value = newValue; return oldValue; } public synchronized boolean compareAndSet(int expectedValue,int newValue){ return (expectedValue == compareAnsSwap(expectedValue, newValue)); } } public class CasCounter { private SimulateCAS value; public int get(){return value.get();} public int increment(){ int v; do{ v = value.get(); }while (v!=value.compareAnsSwap(v,v+1)); return v+1; } } 加锁需要遍历JVM中整个复杂的代码路径.并可能引起系统级加锁,线程挂起以及上下文切换,而CAS不会调用到JVM的代码,系统调用或者调度活动. CAS的缺点: 它强迫调用者处理竞争(通过重试,回退或者放弃),然而在锁被获之前,却可以通过阻塞自动处理竞争. 非阻塞(noblocking) 一个线程的失败或挂起不应该影响其他线程的失败或挂起,这就叫非阻塞. 锁自由(free-lock): 如果算法的每一步骤中都有一些线程能够继续执行,那么这样的算法称为锁自由. 在线程间使用CAS进行协调,这样的算法如果构建正确.那么他即是 非阻塞的也是 锁自由 public class ConcurrentStack<E> { AtomicReference<Node<E>> top = new AtomicReference<>(); public void pust(E item){ Node<E> newHead = new Node<>(item); Node<E> oldHead; do{ oldHead = top.get(); newHead.next = oldHead; }while (!top.compareAndSet(oldHead,newHead)); } public E pop(){ Node<E> newHead; Node<E> oldHead; do{ oldHead = top.get(); if(oldHead == null) return null; newHead = oldHead.next; }while (!top.compareAndSet(oldHead,newHead)); return oldHead.item; } private static class Node<E>{ public final E item; public Node<E> next; public Node(E item) { this.item = item; } } } ------------------------------------------------------------------------------------------------------ ConcurrentStack<String> s = new ConcurrentStack<>(); ExecutorService service = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; ++i) { service.submit(() -> s.pust(Thread.currentThread().getName())); } sleep(1); System.out.println(s.pop()); System.out.println(s.pop()); System.out.println(s.pop()); System.out.println(s.pop()); System.out.println(s.pop()); System.out.println(s.pop()); service.shutdown();
Dillinger Dillinger is a cloud-enabled, mobile-ready, offline-storage, AngularJS powered HTML5 Markdown editor. Type some Markdown on the left See HTML in the right Magic New Features! Import a HTML file and watch it magically convert to Markdown Drag and drop images (requires your Dropbox account be linked) You can also: Import and save files from GitHub, Dropbox, Google Drive and One Drive Drag and drop markdown and HTML files into Dillinger Export documents as Markdown, HTML and PDF Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email. As [John Gruber] writes on the Markdown site The overriding design goal for Markdown'sformatting syntax is to make it as readableas possible. The idea is that aMarkdown-formatted document should bepublishable as-is, as plain text, withoutlooking like it's been marked up with tagsor formatting instructions. This text you see here is actually written in Markdown! To get a feel for Markdown's syntax, type some text into the left window and watch the results in the right. Tech Dillinger uses a number of open source projects to work properly: [AngularJS] - HTML enhanced for web apps! [Ace Editor] - awesome web-based text editor [markdown-it] - Markdown parser done right. Fast and easy to extend. [Twitter Bootstrap] - great UI boilerplate for modern web apps [node.js] - evented I/O for the backend [Express] - fast node.js network app framework [@tjholowaychuk] [Gulp] - the streaming build system Breakdance - HTML to Markdown converter [jQuery] - duh And of course Dillinger itself is open source with a public repository on GitHub. Installation Dillinger requires Node.js v4+ to run. Install the dependencies and devDependencies and start the server. $ cd dillinger $ npm install -d $ node app For production environments... $ npm install --production $ NODE_ENV=production node app Plugins Dillinger is currently extended with the following plugins. Instructions on how to use them in your own application are linked below. Plugin README Dropbox [plugins/dropbox/README.md] [PlDb] Github [plugins/github/README.md] [PlGh] Google Drive [plugins/googledrive/README.md] [PlGd] OneDrive [plugins/onedrive/README.md] [PlOd] Medium [plugins/medium/README.md] [PlMe] Google Analytics [plugins/googleanalytics/README.md] [PlGa] Development Want to contribute? Great! Dillinger uses Gulp + Webpack for fast developing.Make a change in your file and instantanously see your updates! Open your favorite Terminal and run these commands. First Tab: $ node app Second Tab: $ gulp watch (optional) Third: $ karma test Building for source For production release: $ gulp build --prod Generating pre-built zip archives for distribution: $ gulp build dist --prod Docker Dillinger is very easy to install and deploy in a Docker container. By default, the Docker will expose port 8080, so change this within the Dockerfile if necessary. When ready, simply use the Dockerfile to build the image. cd dillinger docker build -t joemccann/dillinger:${package.json.version} This will create the dillinger image and pull in the necessary dependencies. Be sure to swap out ${package.json.version} with the actual version of Dillinger. Once done, run the Docker image and map the port to whatever you wish on your host. In this example, we simply map port 8000 of the host to port 8080 of the Docker (or whatever port was exposed in the Dockerfile): docker run -d -p 8000:8080 --restart="always" <youruser>/dillinger:${package.json.version} Verify the deployment by navigating to your server address in your preferred browser. 127.0.0.1:8000 Kubernetes + Google Cloud See KUBERNETES.md Todos Write MORE Tests Add Night Mode License MIT Free Software, Hell Yeah!
1 G1 的基本概念 1 : HotSpot:现有的垃圾回收器: Serial GC, Parallel GC ,Concurrent Mark Sweep Gc这三个GC不同: 1:如果你想要最小化的使用内存和并行开销:选Serial GC2:如果你想要最大化应用程序的吞吐量选用Parallel GC3:如果想要最小化GC的中断或停顿时间选CMS GC 2 G1是Garbage First, 意思: G1是一个并行回收器,它把内存分割为很多不相关的区间(Region),每一个区间可以属于老年代或年轻代,并且每个区间上是可以物理上不连续的,老年代区间这个设计理念本身是为了服务于并行后台线程,这些线程的主要工作是寻找未被引用的对象,而这样就会产生一种现象,即某些区间的垃圾多余其它区间,(垃圾回收时都是需要停下应用程序,不然没办法防止应用程序的干扰),然后G1可以集中精力在垃圾最多的区间上,并且只需要一点点时间就可以清空这些区间垃圾,腾出完全空闲的时间.总而言之:G1侧重点在于处理垃圾最多的区间,所以叫Garbege First 3 : G1内部主要有四个操作阶段: 1:年轻代回收 (A young Collection)2: 运行在后台的并行循环(A background Concurrent Cycle)3: 混合回收(A mixed Collection)4: 全量回收 (A full GC) 2 进程和线程 1: 子进程和父进程有不同的代码和数据空间,而多个线程共享数据空间,每个线程都有自己的执行堆栈和程序计数器为其执行上下文 3 ava###1 G1 的基本概念 1 : HotSpot:现有的垃圾回收器: Serial GC, Parallel GC ,Concurrent Mark Sweep Gc这三个GC不同: 1:如果你想要最小化的使用内存和并行开销:选Serial GC2:如果你想要最大化应用程序的吞吐量选用Parallel GC3:如果想要最小化GC的中断或停顿时间选CMS GC *2 G1是Garbage First, 意思: G1是一个并行回收器,它把内存分割为很多不相关的区间(Region),每一个区间可以属于老年代或年轻代,并且每个区间上是可以物理上不连续的,老年代区间这个设计理念本身是为了服务于并行后台线程,这些线程的主要工作是寻找未被引用的对象,而这样就会产生一种现象,即某些区间的垃圾多余其它区间,(垃圾回收时都是需要停下应用程序,不然没办法防止应用程序的干扰),然后G1可以集中精力在垃圾最多的区间上,并且只需要一点点时间就可以清空这些区间垃圾,腾出完全空闲的时间.总而言之:G1侧重点在于处理垃圾最多的区间,所以叫Garbege First*3 : G1内部主要有四个操作阶段: 1:年轻代回收 (A young Collection)2: 运行在后台的并行循环(A background Concurrent Cycle)3: 混合回收(A mixed Collection)4: 全量回收 (A full GC) 2 进程和线程 1: 子进程和父进程有不同的代码和数据空间,而多个线程共享数据空间,每个线程都有自己的执行堆栈和程序计数器为其执行上下文 3 java 里面的4中引用类型 1: 强引用 在一个线程内,无需直接引用直接可以使用的对象,除非引用不存在了,否则不会被GC清理了, ==操作用于表示两个操作数所指向的堆地址空间地址是否相同,不表示两个操作数所指向的对象是否相等2: 软用于,JVM抛出OOM之前,GC会清理所有的软引用对象,垃圾回收器在莫个时刻决定回收软可达的对象的时候,会清理软引用,并可选的把引用放到一个引用队列(Reference Queue),类似弱引用,只不过java虚拟机会尽量让软引用存活时间长一点,破不得一才清理.3弱引用 (Weak Rederence) 弱引用和软引用的最大不同在于:当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收.弱引用对象更容易,更快被GC回收.GC在运行时一定回收弱引用对象,但是关系复杂的弱引用对象群常常需要好几次GC的运行才能完成4虚引用(Phantom Reference): 主要目的是在一个对象所占用的内存被实际回收之前得到通知,从而进行一些相关清理工作,虚引用在创建时必须提供一个引用队列作为参数,其次虚引用对象的get方法总是返回null,因此无法通过虚引用来获得被引用的对象5:finalization机制: finalize方法和c++中的析构函数类似,但是java采用的是基于垃圾回收器的自动内存管理机制,所以finalize方法在本质上不同于c++的析构函数,当垃圾回收器发现没有引用指向一个对象时,会调用这个对象的finalize方法,通常这个方法会进行一些资源释放和清理的工作,比如关闭文件,套接字或数据库链接 由于finalize方法存在,虚拟机中的对象一般处于三种可能的状态 (1)可达到状态(2)可复活状态(腹泻了finalize方法)(3)不可到达状态,垃圾回收器自由释放对象所占用内存finalize()只会被JVM自动调用一次,也就是只有一次拯救自己的机会 4并行 在Hotpot垃圾搜集器里除了G1之外,其他的垃圾收集器使用的是内置的JVM 线程执行GC的多线程操作,而G1 GC 采用的是应用线程承担后台运行的GC工作,即当VM线程线程处理速度慢时,系统会调用应用线程帮助加速垃圾回收过程.并发在JVM里面是指垃圾收集线程和用户线程同时执行,但不一定是并行执行,可能是交叉执行,用户线程继续运行,而垃圾手机线程运行在另一块CPU上. 5 分代 1: heap上的分代: Eden survivor spaces virtual1(称Young) --> ... Virtual1(Tenured) --> Perm(存放Class and Meta) (Young代MinorGC,每执行一次,对象年龄+1, (Tenured区叫Full GC) 注意GC不会在主程序运行期对PernGen Space进行清理,如果应用程序会加载很多类的时候,就可能出现PermGen Space 错误,例如加入的第三方包太多导致2: jdk7的更新过程中,永久区还是存在并使用的,只不多已经开始从他内部移除数据了,大致一共移除三类数据:(1) 例如&APPLID这样的符号(Symbols)被移到了本地堆区(2)内部字符串被移到了java堆区(3)类的静态属性被移到了Java堆区如果你使用jdk7而使用G1 GC ,那么永久区只有在Full GC阶段才会被回收,G1只会在永久区满了之后才会调用Full GC事件,或者在应用程序的生产速度比G1的垃圾回收速度块时调用JDK8 JVM 开始使用本地化的内存空间来存放元数据,这个空间叫做元空间(Meta Space)这样修改就意味着OOM:PermGen的问题不存在了,并且不需要调整和监控这个空间,JDK8完成了对永久区的移除 6 Full Gc的条件(除了System.gc) 1: 老年代空间不足,当执行Full GC之后任然不足的会抛出如下 OOME:Java heap space2:永久代空间满,永久代中存放一些类的信息,当系统中要加载的类,反射的类和调用的方法较多时,永久代可能会占满,执行Full GC之后任然回收不了会抛出OOME:PermGen space3:CMS GC时出现Promotion Failed and Concurrent Mode Failure 尤其要注意GC日志中有这两种情况很可能会出现Full GC,PromotionFailed是在进行MinorGC时,SurvivorSpace放不下了,对象只能放入老年代,而此时老年代也放不下造成的. Concurrent Mode Failure 是在执行CMS GC的过程中,同时有对象要放入老年代,而此时老年代空间不足造成的应对措施:增大Survivor Space,老年代空间或调低并发GC的比率 4:统计得到的MinorGC晋升到老年代的平均大小大于老年代的剩余空间 7 java中GC Roots包括以下内容: 1: 虚拟机栈中引用的对像2:方法区中类静态属性实体引用的对象3:方法区中常量引用对象4:本地方法栈内JNI引用的对象 8 GC 参数 1 G1之前: -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails2: G1 : -XX:+UseSerialGC -Xlog:gc:gc.log -Xlog:gc* 9 堆外内存(off-heap memory) JVM内部会把内存分为Java使用的堆内存和native使用的内存,他们之间是不能共享的,就是说当native内存用完了时,如果Java内存堆有空闲,这是native回向jvm申请而不是直接使用Java堆内存线程栈,应用程序代码,NIO缓存用的都是堆外内存,事实上,c/c++中,使用的也是未托管内存,在Java中使用托管内存或者堆内存是这门语言的一个特性.使用堆外内存和对象池都能减少GC的暂停时间.堆外内存的好处: 1: 可以扩展更大的内存空间,比如超过1TB甚至比主内存还大的空间2: 理论上可以减少GC暂停时间3:可以在进程间共享,减少JVM间对象复制,使得JVM的分割部署更容易实现4:持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据 堆外内存的缺点: 1:数据结构变得有些扭曲2:可能需要一个简单的数据结构以便于直接映射到堆外内存3:使用复杂的数据结构并序列化和反序列化到内存中4:序列化比使用对象的性能差很多 与其他方案比较,比如堆缓存,消息队列,使用堆外内存更简单,高效. 堆外内存可以支持操作系统的同步写入,不需要异步去执行,但可能丢失数据,堆外内存的好处是能够缩短映射时间,映射1TB的数据只需要10ms 10 不推荐使用finalize释放资源: 1:在 finalize()时可能导致对象复活2:finalize()函数执行是没有时间保证的,她完全由GC线程决定,极端情况下,若不发生GC,则finalize()函数不执行3:一个糟糕的finalize()会严重影响GC的性能 函数finalize() 是由FinalizerThread线程处理的,方法的对象都会在被正式回收前加入FinalizerThread的执行队列,该队列由链表实现... 11 G1涉及术语 MetaSpace jdk8 HotSpot JVM 使用本地内存来存储类元数据信息并称为元空间(java8移除了永久区的概念,转而使用元空间)默认情况下,大部分类元数据都在本地内存中分配,类元数据只受可用的本地内存限制,并动态调整,新参数(MaxMetaspaceSize用来限制元数据的空间大小)-XX:MetaspaceSize 初始化元空间大小(默认12M(32位JVM) ,16M(64位JVM))-XX:MaxMetaspaceSize 最大元空间大小(默认本地内存)-XX:MinMetaspaceFreeRatio 扩大空间的最小比例,当GC过后,内存占用超过这一比例,就会扩大空间.-XX:MaxMetaspaceFreeRatio: 缩小空间的最小比例,当GC后,内存占用低于这一比例,就会缩小空间.Reclaimable G1 GC 为了能够回收,创建了一系列专门用于存放可回收对象的Region,这些Region都在一个链表队列里面.这个队列只包含存活率小于 -XX:G1MixedGCLiveThresholdPercent(默认85%)的Region, Region的值除以整个Java堆区,如果大于-XX:G1HeapWastePercent(默认5%)则启动回收机制RSet 全称是 RememberdSet 简称是RSet: 指跟踪指向某个堆区(Region)内的对象引用,在标记存货对象时,G1使用RememberSet的概念,将每个分区外指向分区内的引用记录在该分区的RememberSet中,避免了对整个heap的扫描,使得各个分区更加独立.堆内存中每个分区都有一个RSet,Rset的作用是让堆区能并行独立的进行垃圾集合.Rset所占用的JVM内存小于总大小的5%CSet Collection Set 简称CSet即收集集合,保存一次GC中将执行垃圾回收的区间(Region),GC时在CSet中的所有存活数据(Live Data) 都会被转移(复制/移动).集合中的堆区可以是Eden Survivor或Old Generation. CSet所占用的JVM内存小于总大小的1%.可以看出: CSet相当于一个大圈,里面包含了很多小圈(Rset).这些圈圈都是需要被回收的信息,这样可以把CSet比作垃圾场,Rset是垃圾场里面一个个绿色可回收垃圾桶.G1 Pause Time Target: G1停顿目标时间,由于垃圾收集阶段可能是独占试的,就会引起应用程序的停顿,这个停顿时间就是你所设置的期望时间.G1使用一个停顿预测模型去匹配用户设定的目标停顿时间,并且基于这个目标停顿时间去选择需要回收的Region的数量Root Region Scan 这个阶段从根区间的扫描开始,标记所有可达的存活对象.由于在并行标记的执行过程中移动数据会造成应用程序的暂停.所有根区间扫描这个阶段需要在下一次评估中断开始执行直到结束.PLAB (Promotion Local Allocation Buffers) 它被用于年轻代回收.PLAB的`作用是避免多线程竞争相同的数据.处理方式是每个线程拥有独立的PLAB,用于针对幸存者和老年空间.当应用开启的线程较多时,最好使用 -XX:-ResizePLAB来关闭PLAB()的大小调整,以避免大量的线程通信导致的性能下降.TLAB (Thread Local Allocation Buffers) 即线程本地分配缓存,是一个线程专用的内存分配区域. 总的来说TLAB是为了加速对象分配而生的,由于对象一般会分配在堆上,而堆是全局共享的.因此在同一时间,可能会有多个线程在堆上申请空间.JVM使用TLAB这种线程专属的区间来避免多线程冲突,提高对象分配效率,TLAB本身占用了eden区的空间,即JVM会为每一个Java线程分配一块TLAB空间.对于G1来说,TLAB是Eden的一个Region,被一个单一线程用于分配资源.主要用途是让一个线程通过栈操作方式独享内存空间,用于对象分配.这样比多个线程之间共享资源要快得多. Lock-free Manner: TLAB调用本地线程的无锁竞争分配方式.对于GC来说,堆内存的压缩和栈检索通常是引起性能问题的瓶颈爆发点,基于锁的机制扩展能力不好,所以采用CAS/MCAS同步方式可以确保不会被其他线程阻塞.Region G1收集器将堆进行分区,划分一个个区域,每次收集的时候,只收集其中几个区域,以此来控制垃圾回收产生的停顿时间,这就是Region从字面来说Region表示一个区域,每个区域里面的字母代表不同的分代内存空间类型,如:[E] Eden [O] Old [S] survivor ,空白的区域不属于任何一个分区,G1 可以在需要的时候任意指定这个区域属于Eden或是O区之类的G1通过将内存空间分成区域(Region)的方式避免内存碎片问题,每个Region是大小一致的,从逻辑层面来说,他们又是连续的虚拟内存块,G1的并行全局标记阶段会决定整个堆区的存活对象,在这个标记阶段完成之后,G1就知道哪些Region是空的了.Ergonomics Heuristic Decision 字面意思是人体工程学,可以理解为适合人类理解的行为或习惯Evacuation Failure 可以理解为提升失败(Promotion Failre) ,to-space exhaustion to-space overflow这个异常通常发生在提升对象时发现内存空间不足.Top-at-mark-start 每个区域都记录这两个TAMS指针(top-at-mark-start),分别为 prevTAMS,nextTAMS,在TAMS以上的对象时新分配的,因而被视为隐式标记. 12 Java虚拟机内存模型 1 jvm将内存数据分为5个部分: 程序计数器 虚拟机栈 本地方法栈 Java堆 方法区简单来说: 程序计数器用于存放下一条运行时指令,虚拟机栈和本地方法栈用于存放函数调用堆栈信息,Java堆用于存放Java运行时所需要的对象等,方法区用于存放程序的类元数据信息 线程共享区域(堆内存区,方法区,运行时常量池)-XSS设置虚拟机栈的大小,栈里面存放栈帧(Stack Frame)栈帧里面存放:局部变量表,操作数栈,动态链接方法和返回地址等信息栈帧由3部组成: :局部变量区(Local Variable) ,操作数栈(Operand Stack) ,帧数据区(Frame Data) Escape Analysis(逃逸分析):指分析指针动态范围的方法,他与编译器优化原理的指针分析和外形分析相关联,通俗点:如果一个对象的指针被多个方法或线程引用时,那我们可以称这个指针发生了逃逸.一般出现在三种场景:全局变量赋值 方法返回值 实例引用传递 static class B{ public void printClassName(G g){ System.out.println(g.getClass().getName()); } } static class G{ public static B b; public void globalVarPointEscape(){ b = new B();//给全局变量赋值,发生逃逸 } public B methodPoint(){ return new B();//方法返回值,发生逃逸 } public void instancePassPointer(){ methodPoint().printClassName(this);//实例引用发生逃逸 } } java里面缺少像C#里面的值对象或者C++里面的struct,栈里面只保存了对象指针,当对象不再使用后需要依靠GC来遍历引用树并收回内存,如果对象较多,会影响性能*Java7开始支持对象的栈分配和逃逸分析机制,此外,逃逸分析还有其他2个优化应用,同步消除 矢量代替 ,同步消除:逃逸分析可以判断出某个对象是否只被一个线程访问,如果只被一个线程访问.那么该对象的同步操作会转化为没有同步操作,提高性能, 矢量代替:逃逸分析方法如果发现对象内存储结构不需要连续进行的话,就可以将对象的部分或者全部都保存在CPU寄存器内 Java7完全支持栈式分配对象,JIT支持逃逸分析优化,还默认支持OpenGL的加速功能方法区:主要保存的信息是类的元数据,和堆空间类似,也是被JVM所有线程共享,包括 类型信息(类的完整名称,父类完整名称,类型修饰符), 常量池, 方法信息,和 类型的直接接口类表` *在HotSpot虚拟机中*方法区也被称作是永久区,是一块独立于堆空间的内存空间 永久区GC: (1)对永久区常量池的回收(2)类元数据的回收 三种常见的垃圾回收算法: (Mark-Sweep) 标记清除 ,复制算法(Copying) 标记压缩(Mark-Compact) mutator and colletor:collector就是指的垃圾收集器,而mutator是指垃圾收集器之外的部分,例如我们的应用程序本身,mutator指责就是NEW(分配内存) READ(从内存中读取内容) WRITE(将内容写入内存) ,而colletor则是回收不再使用的内存来供mutator进行NEW操作,mutator根对象一般指的是分配在堆内存之外,可以直接被mutator直接访问到的对象,一般指静态/全局变量以及ThreadLocal变量 标记清除: 分为2个阶段: 标记和清除 Mark-Sweep Mark-Compact Copying 速度 中等 最慢 最快 空间开销 少(但会积累碎片) 少(没有碎片) 通常需要活动对象的2倍大小 移动对象 否 是 是 标记清除最大缺点: 存在大量的空间碎片. 复制算法: 复制效率高,但是内存缩小一半,一般采用分代: java heap 分为 YounGen and OldGen, 其中Young分为Eden s0 s1 ,其比例为8:1 , 标记压缩: 一般用在老年代中,标记清除算法不仅效率底下而且执行完还有内存碎片,JVM对此进行了改进产生了标记压缩,先标记再压缩,老年代执行full gc 增量算法(Incremental Collecting): 思路简单,一个线程收集另一个线程执行应用程序,这样就不会stop-the-world, 但是有缺陷,一个线程辛辛苦苦标记的垃圾会被另一个线程影响.增量算法的基本思想是:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用线程交替执行.缺陷就是线程切换和上下文转换消耗,会使得垃圾收集成本上升造成系统吞吐量下降. 分代收集算法:(Generational Collecting) : 基于对象生命周期分析后得出的垃圾回收算法.他把对象分为,年轻代,老年代和持久代,对不同生命周期的对象使用不同的算法进行回收. JVM(j2se 1.2)都是使用此算法. 评估GC的性能 属性 描述 吞吐量 程序的运行时间(程序运行时间+内存回收时间) 垃圾收集开销 吞吐量的补数,垃圾收集器所占时间与总时间比例 暂停时间 执行垃圾收集,程序的工作线程被暂停时间 收集频率 相对于应用程序的执行,收集操作发生的频率 堆空间 Java堆区所占的内存大小 快速 一个对象从诞生到被回收所经历的时间 垃圾收集器分类 Serial/Serial Old ParNew Parallel Old CMS (Concurrent-Mark-Sweep) G1(garbage first) 垃圾收集器的分类 Serial 收集器: 0: 串行回收和 stop-the-world1: -XX:+UseSerialGC 手动指定使用Serial收集器执行内存回收任务.2: 该算法步骤: (1)在老年代标记存活对象(2)从头开始检查堆内存空间,并且只留下依然幸存的对象(清除)(3)从头开始,顺序的填满堆内存空间,将存活的对象连续存放一起,这样堆分成2部分,一边有存放的对象,一遍没有对象(整理) (4) : Serial收集器应用于小的存储器和少量的CPU 年轻代串行收集器 在HotSpot虚拟机中,使用-XX:+UseSerialGC 参数可以指定使用年轻代串行收集器和老年代串行收集器,当JVM在client模式下运行,它是默认的垃圾收集器 老年代串行收集器 老年代串行收集器使用的是标记压缩算法.和年轻代串行收集器一样,也是独占式串行收集器,但是老年代一旦启动垃圾收集,应用程序会停顿好几秒,也可以使用 -XX:+UseParNewGC,指定年轻代使用并行收集器老年代使用串行收集器 ParNew 收集器 ParNew 是Serial收集器的多线程版本.基本没有区别,如果使用 -XX:+UseParallelGC 表示年轻代使用并行回收器,老年代使用串行收集器 ,ParallelGC和ParNew不同,Parallel可以控制程序吞吐量大小,因此他也称为吞吐量优先的垃圾收集器.可以使用-XX:GCTimeRatio设置执行内存回收的时间所占用JVM运行总时间比例,也就是控制GC执行频率.公式为1/(1+N).默认值为99/也就是只有1%的时间用于执行垃圾回收. CMS收集器(非独占式): 1: 一个优秀的老年代垃圾收集器. 低延迟是他的优势,算法采用标记清除,因此也会有 'Stop-The-World'机制而出现短暂暂停. 2: CMS执行的四个阶段: 初试标记(Init-Mark) 并发标记(Concurrent-Mark) 再次标记(Remark) 和并发清除(Concurrent-Sweep) 3: 尽管CMS收集器采用的是并行回收,但是初始化和再标记阶段任然要STW机制暂停工作线程,不过时间不长. 4: 前面的几个老年代收集器 Serial Old and Parallel Old收集器都采用算法都是标记清除,因此没有fullgc之后的内存碎片,而CMS采用的是标记清除,意味着每次执行完会有内存碎片.那么CMS在为新对象分配内存之后,将无法使用指针碰撞(Bump the pointer)技术.而只能选择空闲列表(Free List) 执行内存分配. G1 收集器 1: G1 GC 切分堆内存为多个区间(region),从而避免了很多GC操作在整个Java堆或者整个年代进行. 2: 在JVM启动的时候不需要立即指定哪些Region属于年轻代,哪些Region属于老年代,因为他们都不需要一大块连续的内存. 3: G1的年轻代收集阶段是一个并行的独占式收集器.当一个年轻代进行收集时,整个年代都会被收回,所有的应用线程会被中断,G1 GC会启用多线程执行年轻代回收, 和年轻代不同,老年代G1回收器和其他Hotspot不同,G1的老年代回收器不需要整个老年代被收回,一次只需要扫描/收回一小部分老年代Region就行了,需要注意的是,这个老年代Region是和年轻代一起被收回的 4: G1 把整个Java堆划分为若干个区间(Region),每个region大小为2的倍数.范围在1M-32M之间.所有的region有一样的大小,在JVM生命周期内不会被改变. CSet包含一系列的Region,每一个Region有一个Rset,这个Rset包含了相应Region里面所有指针的位置集合.Rset大小和Region数量有直接关系.一般来说RSet的大小占整个Java堆空间的1%-20% 5: 可用Region:就是unused Region,Eden Region SurvivorRegion 组成了年轻代空间. 6: G1对于大对象有特殊的分配方式,一个大对象是指该对象的大小超过一个Region大小的50%以上,这个大小包含了Java对象头. 全垃圾收集(Full Garbage collection) 1: G1 的FULL GC和Serial GC的Full GC 采用的是同一种算法.Full GC 会对整个Java堆进行压缩,G1的Full GC 是单线程的,会引起较长时间的停顿.因此G1的设计目标就是减少FUll GC的发生次数. 2: 并行循环(Concurrent Cycle) ,一个G1的并行循环包括几个阶段的活动: 初试标记(initialmarking) 并行Root区间扫描和清除(Cleanup),除了最后的cleanup阶段以外,其余阶段都属于标记存活对象阶段. 3: 初始标记阶段的目的是收集所有的GC 根(Roots).Roots是一个对象的起源指针. 为了收集根应用,从应用线程开始,应用线程必须停下,所以他是一个独占式.由于一个年轻代GC必须收集所有的Roots,所以G1的初始标记在一个年轻代GC里完成 4: 并行根区间扫描阶段必须扫描和标记所有的幸存者区间的对象引用,这一阶段可以并行执行. 5: 重标记阶段是一个独占式阶段,通常是一个很短的暂停.这个阶段会完成所以的标记工作. 6: 清除阶段:没有包含存活对象的Region会被回收.并随即被加入Region队列.这个阶段的重要意义是最终决定了哪些Region可以进入混合GC ,在G1内部,混合GC是非常重要的释放内存机制,避免了G1出现没有可用的Region的情况发生.否则就会出现Full GC的情况. 堆大小(Heap Sizing) G1的Java堆通常由多个Region组成,设置堆大小的选项和其它的GC一样,都是由 -xms 和 -xmx的配置 以下情况可能会增大堆内存大小 (1):Full Gc 阶段 (2): Young或者Mixed GC发生时,G1 计算GC算费时间与Java线程的花费时间比例,如果-XX:GCTimeRatio 设置GC花费时间很长,则堆大小会增大.这样就会使G1发生GC的频率降低. 这个值默认是9 所有其它HotSpot GC的默认值是99 (3): 如果一个对象分配失败,即便一个GC刚刚结束,G1采取的策略不是立即重复Full GC,而是通过增大堆内存大小,确保对象分配成功 (4): 当GC申请加入一个新的Region时. Java永久区: 1: 类中包含的元数据: 类的层级信息,方法数据和方法信息(字节码,栈,和变量大小),运行时常量,已确定的符号引用和虚方法表. 2: 在Java8之前这些数据都放在 永久区,永久区的垃圾回收和老年代的垃圾回收是绑定的,一旦一个区被占满就进行垃圾回收. 3: Java8之后,没有永久区,这些数据被移到了一个与堆不像连的本地内存区域 ,也就是元空间.这就不会出现永久区内存溢出或者出现泄漏的数据移到交换区这样的事情 4: 元空间的内存管理由元空间虚拟机来完成,之前,对于类的元数据需要不同的垃圾回收器进行处理,`现在只需要执行元空间虚拟机的C++代码即可完成.在元空间中,类和元数据的生命周期和其对应的类加载器是相同的. 换句话说: 只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉 5: 每一个类加载器的存储区域都称作一个元空间.所有的元空间和在一起就是我们一直说的元空间,当一个类加载器被垃圾回收标记为不再存活时,其对应空间会被回收,在元空间回收过程中没有重定位和压缩等操作.但是元空间内的元素会进行扫描来确定Java应用.-6: 元空间虚拟机复制元空间的分配,其采用的形式为组块分配.组块的大小因类加载器的类型而异.在元空间虚拟机中存在一个全局的空闲组块列表.当一个类加载器需要组块时,他就会从这个全局的组块列表中获取并维持一个自己的组块列表,当一个类加载器不再存活时,其持有的组块将会被释放.并返回给全局列表.类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息.组块中的块是线性分配的(指针碰撞分配形式),组块分配自内存映射区域.这些全局的虚拟内存映射区域以链表形式链接.一旦某个虚拟内存映射区域清空,这部分内存就会 操作系统. 7: 元空间虚拟机控制元空间的增长, 可以用: -XX:MaxMetaspaceSize来设定,默认没有限制.因此元空间可以延伸到交换区,但是这时候进行本地内存分配会失败. 8: 元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定.类信息并不是固定大小的,因此可能分配的空闲区块和类需要的区块大小不同.这种情况可能导致碎片存在.元空间虚拟机目前不支持压缩操作,所以碎片化是目前最大的问题 13 GC 的参数调试 输出样式 输出 参数 使用场景 是否独占 串行收集器 输出DefNew -XX:+UseSerialGC() 年轻代老年代都使用 是 并行收集器 输出ParNew -XX:+UseParNewGC() 年轻代(并行)老年代(串行) 部分 PSYoungGen -XX:+UseParallelOldGC() 年轻代老年代都使用并行 部分 PSYoungGen -XX:+UseParallelGC() 年轻代(并行)老年代使用CMS 部分 GarbageFirst heap -XX:+UseG1GC() 特殊收集器 部分 一般来说, 只有在嵌入式产品下 才使用SerialGC - - 使用java -X可以输入参数意思 -Xloggc --> -XX:+PrintGCDetails -verbose:gc -Xloggc:gclog -XX:+UseSerialGC -verbose -Xlog:gc:gc.log -Xlog:gc* -Xlog:gc:gc.log -Xlog:gc* -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions: 在GC里面有一些选项称之为诊断选项(Diagnostic Options), 可以通过这俩个选项组合起来运行.就可以输出并查看这些选项. 15 深入理解G1 GC G1 GC采用递增,并行运算,独占式的特征方式,并采用拷贝技术实现自身的压缩目标,同时通过并行的多级标记的方式缩短各层级(标记,重标记,清除等阶段)的停顿时间. 在G1中 堆被分成若干个大小相等的Region,每个Region都有一个相关联的RememberSet简称RSet,RS里面的数据结构是Hash表,里面的数据时Card Table(堆中每512个Byte映射在card table 1 byte).简单地说,RS里面存在的都是Region中存活对象的指针,当region中数据发生变化时,首先反映到Card Table中的一个或多个Card上.RS通过扫描内部Card Table得知Region中内存使用和活动对象.在使用R给哦你的过程中,如果Region被填满了.分配内存的线程会重新选择一个新的Region.空闲Region被组织到一个基于链表的数据结构(LinkedList)里面,这样可以快速找到新的Region. G1 的收集过程涵盖4个阶段: 年轻代GC 并发标记周期 混合收集 Full GC 堆区间大小也决定了什么对象可以认定为大对象,大对象通常是很大的对象,占据了超过G1 GC区间的50%,大对象没有按照年轻代的回收方式把对象放入老年区,而是放置在老年区以外的大对象区间里面,即单独分离出来形成一片新的区域,独立管理, 大对象在堆内存里是物理连续的,所以在回收阶段尝试去频繁的回收大对象,存在2个缺点: (1)移动对象需要 拷贝数据,大对象较大,拷贝效率会成为瓶颈.(2)很难找到连续的堆内存用于大对象存储,回收越频繁,越容易出现互相独立的区间. 大对象并没有包含在年轻代或老年代存储空间里,所以不能采用年轻代针对TLAB 和 PLAB的分配或者优化措施. 任何一次垃圾回收都会释放CSet里面所有空间.一个CSet由一系列的等待回收区间所组成 RSet是一个数据结构,这个数据结构帮助维护和跟踪在他们单元内部的对象引用信息,在G1里这个单元就是Region,G1里面的每一个RSet对应的是一个区间内部的对象引用情况,有了RSet就不需要扫描整个堆内存了,当G1 执行STW独占式回收(年轻代,混合回收)时,只需要扫描每一个区间内部的RSet就可以了.因为所有RSet都保存在CSet里面,即: *Region-RSet-CSet*这样的概念.所有一旦区间内部存活对象被移除,RSet里面保存的引用信息也会被立即更新.这样我们就可以认为RSet就是一张虚拟的对象引用表了...每个区间内部都有这么一张表存在,帮助对区间的内部的对象存活情况,基本信息做有序高效的管理 Region并不是最小的单元,每个区间会被进一步划分为若干个块(Chunks),在G1区间里,最小单元是512字节的堆内存块(Card) ,G1为每个区间设置一个全局内存表快来帮助维护所有的堆内存块. RSet帮助维护和跟踪指向G1区间的引用,而这些区间本身拥有这些RSet, 并行Refienment线程的任务是扫描更新日志缓存,并且更新区间RSet,为了更加有效的支援这些Refinement线程的工作.在并行回收阶段,所有未被处理的缓存(已经有日志写在里面了)都会被工作线程拿来处理.这些缓存也被称为日志里面的处理缓存
js 注意点: 1: this 既不是指向函数自身也不是指向函数的词法作用域,this实际上是在函数被调用时发生的绑定,他指向什么完全取决于函数在哪里被调用.2: 函数调用位置就是函数在代码中被调用的位置(而不是声明的位置)
java7相关的注意点 Path path并不仅仅限于传统的文件系统,也能表示zip或者jar文件path 不一定真实的文件或目录,可以随意的创建path中可能有其他冗余项,比如符号链接,获取真实位置可以使用toRealPath() 或者normalize()合并两个path 使用 resolve() ,或者获取相对位置relative()转化Path file.toPath() path.toFile() 处理单个文件目录 String path = "F:\\mavenproject\\SpringProject"; Files.newDirectoryStream(Paths.get(path),"*.xml") .forEach(e-> System.out.println(e.getFileName())); 递归处理文件件 static class FileFindXMLVisitor extends SimpleFileVisitor<Path>{ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if(file.toString().endsWith(".xml")&& (!file.toString().startsWith("Maven"))) System.out.println(file.getFileName()); return FileVisitResult.CONTINUE; } } String path = "F:\\mavenproject\\SpringProject"; Files.walkFileTree(Paths.get(path),new FileFindXMLVisitor()); Asynchronous IO 这是将来式,例如用Future.get()返回结果 如果在创建AsynchronousFileChannel 时没有为其指明线程池,那就会为其分配一个系统默认的线程池(可能会和其他通道共用),默认线程池是由AsynchronousChannelGroup类定义的系统属性进行配置的. 回调式 主线程回派一个侦查员CompletionHandler到独立的线程中执行IO操作,这个侦查员会将IO操作的结果返回到主线程中,这个结果会触发他自己的completed或者failed方法(一般重写这两个方法) String filepath = "C:\\code\\temp.txt"; Path filePath = Paths.get(filepath); AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(filePath); ByteBuffer byteBuffer = ByteBuffer.allocate(1000); fileChannel.read(byteBuffer, 0, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("Bytes read: "+result); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println(exc.getMessage()); } }); 并发安全 Java线程模型建立在(1)共享的,默认可见的可变状态`(2)抢占式线程调度` 1.所有线程可以很容易的共享同一进程中的对象2.能引用这些对象的任何线程都能修改这些对象 设计理念 安全性(并发类型安全) 活跃度 性能 重用性安全性是指: 不管同时发生多少操作都能确保对象保持自相一致 系统开销之源: 锁与监测 环境切换次数 线程的个数 调度 内存的局部性 算法设计 scala 部分 1.类的定义(就他的参数而言)和类的构造方法是同一个东西,scala中也有辅助函数2.类默认是公开的,所以没有必要加上public关键字的3.方法的返回值是通过类型推断确定的,但要在定义方法的def从句中用等号告诉编译器做类型推断4.scala不像java有原始类型,数字也是对象5.关键字object告诉scala这是一个单利类6.scala用方括号来表示泛型,所以类型参数的表示方法是Array[String]而不是String[]7.Array是纯正的泛型8.集合类型必须指明泛型(不能像java那样声明raw type like: List list = new ArrayList )9.单个case不会像java中那样进入下一个case,所以不需要break10.数组的访问用圆括号,例如 args(0)11.类型推断有两个例外(1):方法声明中的参数类型(2):递归函数,scala编译器不能推断递归函数的返回类型12.scala中没有static关键字,必须放在obejct半生对象中13.scala中局部函数,定义在函数内部的函数...不想暴露细节给外部...14.Any的子类AnyVal 和 AnyRef ,AnyVal的子类Unit Boolean Fload Long ,null是AnyRef的所有子类,Nothing是所有(AnyVal和AnyRef)的子类15.scala类必须有个主构造方法来定义该类的所需的参数,辅助构造函数只提供一个默认参数16.如果类里面的方法没有括号,调用时也没有括号17.伴生对象在相关类那里有特权,既可以访问私有函数和私有构造函数18.在创建新的case类实例时,关键字new可以省略,实际上case类的底层实现上是一个创建新实例的工厂方法19.scala默认是不可变集合,但也有可变集合
聚合函数 sum max min avg count group by group by 子句可以包含任数目的字段group by 子句中每个列都是建所列或有效表达式(不能是聚合函数)除了聚会计算语句之外,select语句中的每个列都必须在group中出现如果分组中有null值,则分为一组group by 子句必须在where字句之后,order by子句之前 where过滤行,having过滤的是分组,where没有分组的概念 一般在使用group by子句时,也应该给出order by子句,这是保证数据正确排序的唯一方法,不能依赖group by排序数据例`SELECT order_num,SUM(quantity*item_price) AS ordertotal FROM orderitems GROUP BY order_num HAVING ordertotal >50 ORDER BY ordertotal` 使用子查询 子查询最常用在where和 in 操作中 SELECT cust_name,cust_state,(SELECT COUNT(*) FROM orders WHERE orders.cust_id=customers.`cust_id`) AS orders FROM customers ORDER BY cust_name union规则: union必须有2个select语句组成union的每个查询必须包含相同的列,表达式,聚合函数列数据类型相同 union all 保留重复的数据 union默认去除重复的数据 MySQL的全文索引 Match()指定被搜索的列,Against()指定要使用的搜索表达式Myisam支持全文索引,innodb并不支持创建全文索引fulltext(column)SELECT note_text FROM products WHERE MATCH(note_text) AGAINST('rabbit') 插入数据 insert table(xx) values (xx) 更新数据 update table set xx = xx 删除数据 delete from table where xx=xx 注意 delete并不删除表本身,只是删除一行,删除所有行用 truncate table 创建试图 ,试图本身不包含数据,最常用的就是隐藏复杂的sql,创建格式为create view viewname as select xx 创建存储过程 DELIMITER // CREATE PROCEDURE productpricing() BEGIN SELECT avg(prod_price) AS priceavg FROM products; END // DELIMITER ; CALL productpricing(); DROP PROCEDURE productpricing 存储过程接收三个参数 pl(product lower)产品最低价 ph 最高价,pa平均价 DELIMITER // CREATE PROCEDURE productpricing(OUT pl DECIMAL(8,2),OUT ph DECIMAL(8,2),OUT pa DECIMAL(8,2)) BEGIN SELECT MIN(prod_price) INTO pl FROM products; SELECT MAX(prod_price) INTO ph FROM products; SELECT AVG(prod_price) INTO pa FROM products; END // DEALLOCATE ; CALL productpricing(@pricelow,@pricehigh,@priceavg) SELECT @pricelow low ,@pricehigh high,@priceavg AVG 存储过程接受一个参数,输出一个参数 (In Out) delimiter // create procedure productpricing(IN onnumber Int ,OUT total decimal(8,2)) BEGIN select sum(item_price*quantity) INTO total from orderitems where order_num=onnumber ; end // delimiter ; call productpricing(20005,@total); select @total; 带业务的存储过程 1表示true,0表示false DELIMITER // CREATE PROCEDURE ordertotal(IN number INT,IN taxable BOOLEAN ,OUT ototal DECIMAL(8,2) ) BEGIN -- decalare var for total DECLARE total DECIMAL(8,2); -- decalare tax percentage DECLARE taxrate INT DEFAULT 6; -- get total the order SELECT SUM(item_price*quantity) INTO total FROM orderitems WHERE order_num=number; -- is this taxable IF taxable THEN -- yes SELECT total+(total/100*taxrate) INTO total; END IF; -- and finally save our var SELECT total INTO ototal; END // DELIMITER ; CALL ordertotal(20005,0,@total); SELECT @total; CALL ordertotal(20005,1,@total); SELECT @total; 使用游标 1.使用游标前,必须声明(定义它),这个过程实际没有检索数据,他只是定义要使用的select语句2.一旦声明后就要打开游标已供使用3.在游标结束使用时,必须关闭游标4.打开游标后使用fetch进行使用 创建游标 DELIMITER // CREATE PROCEDURE processorders() BEGIN -- declare var DECLARE done BOOLEAN DEFAULT 0; DECLARE o INT; DECLARE t DECIMAL(8,2); -- declare cursor DECLARE ordernums CURSOR FOR SELECT order_num FROM orders; -- declare continue handler , this is the end of row DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1; -- create table CREATE TABLE IF NOT EXISTS ordertotals(order_num INT ,total DECIMAL(8,2)); -- open cursor OPEN ordernums; -- loop all rows REPEAT -- get order number FETCH ordernums INTO o; -- get total for this order CALL ordertotal(o,1,t); -- insert order and total INSERT INTO ordertotals(order_num,total) VALUES(o,t); -- end of loop UNTIL done END REPEAT; -- close cursor CLOSE ordernums; END // DELIMITER ; CALL processorders(); 使用游标例子2 DELIMITER // CREATE PROCEDURE processorders() BEGIN DECLARE obj INT; DECLARE target DECIMAL (8,2); DECLARE done INT DEFAULT FALSE; DECLARE cur CURSOR FOR SELECT order_num FROM orders; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; CREATE TABLE IF NOT EXISTS order_total(order_num INT,total DECIMAL(8,2)); -- open cursor OPEN cur; read_loop: LOOP FETCH cur INTO obj; IF done THEN LEAVE read_loop; END IF; CALL ordertotal(obj,1,target); INSERT INTO order_total(order_num,total) VALUES(obj,target); END LOOP; -- close cursor CLOSE cur; END // DELIMITER ; CALL processorders(); 触发器的使用 1 .在创建触发器时,需要给出4条信息:1. 唯一的触发器名,2.触发器关联表,3.触发器应该响应的活动(delete,update,insert),触发器何时执行(处理之前或之后)2 . 只有表才支持触发器,试图和临时表不支持触发器 触发器例子(但是MySQL 5.0 之后不能返回结果集) CREATE TRIGGER newproduct2 AFTER INSERT ON order_total FOR EACH ROW SELECT 'products add '; 事物管理 transaction rollback commit save point