Reactor(反应器)模式-为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。当客户端请求抵达后,服务器处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。
首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分法给相应的Request Handler。
netty node.js Java的nio 都是这种设计模式
首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。我们分别以读操作和写操作为例来看看Reactor中的具体步骤:
读取操作:
1. 应用程序注册读就绪事件和相关联的事件处理器
2. 事件分离器等待事件的发生
3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理
写入操作类似于读取操作,只不过第一步注册的是写就绪事件。
下面我们来看看Proactor模式中读取操作和写入操作的过程:
读取操作:
1. 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。
2. 事件分离器等待读取操作完成事件
3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。
Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。
前摄器模式 :前摄器模式支持多个事件处理器的多路分离和分派,这些处理器由异步事件的完成来触发。通过集成完成事件的多路分离和相应的事件处理器的分派,该模式简化了异步应用的开发。前摄器模式实现多亏了多个事件处理器将事件和处理分离开,和处理方式的分派。而这些事件处理器的触发方式是当被异步事件通知而出发的。
当浏览器请求一个文件,事件处理器就会把这个请求告诉操作系统,让操作系统去办,而事件处理器本身只关注操作系统发送回来的完成事件,事件处理器收到完成事件就拿操作系统搬好的砖,交给工头,任务就算完成了。
NIO的通知是发生在动作之前,是在可写,可读之前,Selector发现这些事件后调用Handler处理。
HTTP协议本身是无状态的,需要基于HTTP协议支持会话状态的机制。而这样的机制可以使Web服务器从多次单独的HTTP请求中看到会话,也就是知道请求是从哪个会话的。具体的实现方式:在会话开始时,分配一个唯一的会话标志(sessionID),通过cookie把这个标识告诉浏览器,以后每次请求的时候,浏览器都会带上这个会话标识来告诉web服务器请求是属于哪个会话的。
Java反射:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。Java反射机制主要提供了以下功能:在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
1获取对象属于哪个类
2获取类的信息
3构建对象
4动态执行方法
5动态操作属性
事务:一个事务是一个完整的工作单元,由多个独立的计算机任务组成,这多个任务在逻辑上是原子的。
全局事务:一次性操作多个资源管理器的事务就是全局事务。
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点上。对于传统的单机上的事务,所有的事情都在这一台机器上完成,而在分布式事务中,会有多个节点参与。
消息中间件为我们带来了异步的特性,对系统进行了解耦,对于大型分布式来说有重要的意义。
单例模式:
public class Singleton{
private static Object lock=new Object();
private static volatile Singleton instance=null;
privata Singleton(){}
/*
*version0,非线程安全,当线程A判断instance==null,此时还没有执行new Singleton,然后线程B
*也判断instance==null,这样会导致都创建了Singleton实例
*/
public static Singleton getInstance0(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
/**
*version1,我们可以对getInstance方法加synchronized,这里让锁更加细腻,加在里面
*/
public static Singleton getInstance1(){
synchronized(lock){
if(instance==null){
instance=new Singleton();
}
}
return instance;
/**
*version1,version0有个问题,每次要判断instance==null,都需要进入锁,同步。
*但是单例只是实例化一次,其他时间都是读取实例,这样会影响性能,所以再加入一个判断instance==null
*这样如果单例已经实例化就不需要同步了。
*
*singleton=new Singleton(),这并非是一个院子操作,事实上jvm中大概做了下面3件事情。
1.在堆中分配内存
2.调用Singleton的构造函数来初始化成员变量,形成实例
3.将singleton对象指向分配的内存空间(执行完这步Singleton才是非null)
但是在jvm的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的。最终的执行顺序可能是1->2->3,也可能是1->3->2.
在synchronized同步块里面,仅保证只有一个线程进入,但是有可能发生线程的切换,由于singleton=new Singleton()并非原子的操作,所以在执行步骤1-3时,可能会切换到另外一个线程B,但此时new Singleton()在堆空间还没有初始化,由于Singleton在内存堆中已存在(但尚未初始化),会直接返回singleton引用,但此时引用指向的是未初始化对象
为此需要把singleton声明成volatile变量就可以了。
volatile 有两个功能:
1)这个变量不会在多个线程中存在复本,直接从内存中读取
2)这个关键字会禁止指令重排序优化。也就是说,在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上,读操作不会被重排序到内存屏障之前)
*/
public static Singleton getInstance2(){
if(instance==null){
synchronized(lock){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
/*
*
/* | |
* 使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它, | |
* 因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷; | |
*/ | |
class Singleton1{ | |
private static class SingletonHolder{ | |
private static final Singleton1 INSTANCE = new Singleton1(); | |
} | |
private Singleton1(){} | |
public static final Singleton1 getInstance(){ | |
return SingletonHolder.INSTANCE; | |
} | |
} | |
/* | |
* 使用枚举 | |
*/ | |
enum Singleton2{ | |
INSTANCE; | |
} | |