作者:zuoxiaolong8810(左潇龙),转载请注明出处。
LZ到目前已经写了九个设计模式,回过去看看,貌似写的有点凌乱,LZ后面会尽量改进。
那么本章LZ和各位读友讨论一个与JAVA中IO有着不解情缘的设计模式,装饰器模式。
定义:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
这一个解释,引自百度百科,我们注意其中的几点。
1,不改变原类文件。
2,不使用继承。
3,动态扩展。
上述三句话一语道出了装饰器模式的特点,下面LZ给出装饰器模式的类图,由于LZ的ROSE不是特别好用,各位凑合着看,先上图再解释。
从图中可以看到,我们装饰的是一个接口的任何实现类,而这些实现类也包括了装饰器本身,装饰器本身也可以再被装饰。
另外,这个类图只是装饰器模式的完整结构,但其实里面有很多可以变化的地方,LZ给出如下两条。
1,Component接口可以是接口也可以是抽象类,甚至是一个普通的父类(这个强烈不推荐,普通的类作为继承体系的超级父类不易于维护)。
2,装饰器的抽象父类Decorator并不是必须的。
那么我们将上述标准的装饰器模式,用我们熟悉的JAVA代码给诠释一下。首先是待装饰的接口Component。
package com.decorator; public interface Component { void method(); }
接下来便是我们的一个具体的接口实现类,也就是俗称的原始对象,或者说待装饰对象。
package com.decorator; public class ConcreteComponent implements Component{ public void method() { System.out.println("原来的方法"); } }
下面便是我们的抽象装饰器父类,它主要是为装饰器定义了我们需要装饰的目标是什么。
package com.decorator; public abstract class Decorator implements Component{ protected Component component; public Decorator(Component component) { super(); this.component = component; } }
再来便是我们具体的装饰器A和装饰器B。
package com.decorator; public class ConcreteDecoratorA extends Decorator{ public ConcreteDecoratorA(Component component) { super(component); } public void methodA(){ System.out.println("被装饰器A扩展的功能"); } public void method(){ System.out.println("针对该方法加一层A包装"); component.method(); System.out.println("A包装结束"); } }
package com.decorator; public class ConcreteDecoratorB extends Decorator{ public ConcreteDecoratorB(Component inputStream) { super(inputStream); } public void methodB(){ System.out.println("被装饰器B扩展的功能"); } public void method(){ System.out.println("针对该方法加一层B包装"); component.method(); System.out.println("B包装结束"); } }
下面给出我们的测试类。我们针对多种情况进行包装。
package com.decorator; public class Main { public static void main(String[] args) { Component component =new ConcreteComponent();//原来的对象 System.out.println("------------------------------"); component.method();//原来的方法 ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A System.out.println("------------------------------"); concreteDecoratorA.method();//原来的方法 concreteDecoratorA.methodA();//装饰成A以后新增的方法 ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B System.out.println("------------------------------"); concreteDecoratorB.method();//原来的方法 concreteDecoratorB.methodB();//装饰成B以后新增的方法 concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B System.out.println("------------------------------"); concreteDecoratorB.method();//原来的方法 concreteDecoratorB.methodB();//装饰成B以后新增的方法 } }
下面看下我们运行的结果,到底是产生了什么效果。
从此可以看到,我们首先是使用的原始的类的方法,然后分别让A和B装饰完以后再调用,最后我们将两个装饰器一起使用,再调用该接口定义的方法。
上述当中,我们分别对待装饰类进行了原方法的装饰和新功能的增加,methodA和methodB就是新增加的功能,这些都是装饰器可以做的,当然两者并不一定兼有,但一般至少会有一种,否则也就失去了装饰的意义。
另外,文章开篇就说道了IO与装饰器的情缘,相信各位就算不太清楚,也都大致听说过JAVA的IO是装饰器模式实现的,所以LZ也不再废话,在给出一个标准的模板示例以后,直接拿出IO的示例,我们真枪实弹的来。
下面LZ直接给出IO包中的部分装饰过程,上面LZ加了详细的注释以及各个装饰器的功能演示,各位可以和上面标准的装饰器模式对比一下,LZ不得不感叹,IO与装饰器的孽缘。
package com.decorator; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.PushbackInputStream; import java.io.PushbackReader; public class IOTest { /* test.txt内容: * hello world! */ public static void main(String[] args) throws IOException, ClassNotFoundException { //文件路径可自行更换 final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt"; //InputStream相当于被装饰的接口或者抽象类,FileInputStream相当于原始的待装饰的对象,FileInputStream无法装饰InputStream //另外FileInputStream是以只读方式打开了一个文件,并打开了一个文件的句柄存放在FileDescriptor对象的handle属性 //所以下面有关回退和重新标记等操作,都是在堆中建立缓冲区所造成的假象,并不是真正的文件流在回退或者重新标记 InputStream inputStream = new FileInputStream(filePath); final int len = inputStream.available();//记录一下流的长度 System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported()); System.out.println("---------------------------------------------------------------------------------"); /* 下面分别展示三种装饰器的作用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面做了三个装饰器的功能演示 */ //首先装饰成BufferedInputStream,它提供我们mark,reset的功能
假设存在这样一个情况:需要N个线程对一个全局的变量进行M次递增操作。首先想到的常常是,使用互斥量。当然在“无锁”的世界里,还有其它实现方式。话不多说,看代码:
测试代码gcc_sync_test.c
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define TEST_ROUND 20000 #define THREAD_NUM 10 #define SYNC #define LOCKLESS #ifndef LOCKLESS pthread_mutex_t mutex_lock; #endif static volatile int count = 0; void *test_func(void *arg) { int i = 0; for(i = 0; i < TEST_ROUND; i++){ #ifdef SYNC #ifdef LOCKLESS __sync_fetch_and_add(&count, 1); #else pthread_mutex_lock(&mutex_lock); count++; pthread_mutex_unlock(&mutex_lock); #endif #else count++; #endif } return NULL; } int main(int argc, const char *argv[]) { pthread_t thread_ids[THREAD_NUM]; int i = 0; #ifndef LOCKLESS pthread_mutex_init(&mutex_lock, NULL); #endif for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){ pthread_create(&thread_ids[i], NULL, test_func, NULL); } for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){ pthread_join(thread_ids[i], NULL); } printf("count=%d\r\n", count); return 0; }Makefile
CC=gcc CFLAGS= -Wall LIB= -lpthread OBJS=gcc_sync_test.o SRCS=${OBJS:%.o=%.c} TARGETS=.depend gcc_sync_test all:$(TARGETS) .depend: @$(CC) $(CFLAGS) -MM $(SRCS) > .depend -include .depend gcc_sync_test: $(OBJS) $(CC) $(CFLAGS) $^ $(LIB) -o $@ @echo $@ > .gitignore clean: rm -rf $(OBJS) $(TARGETS) .c.o: $(CC) $(CFLAGS) -c $< -o $@
测试结果
在源代码文件gcc_sync_test.c中,使用SYNC宏来控制是否启用线程间同步;在启用SYNC情况下,使用LOCKLESS宏来控制是否使用“无锁”方式,还是使用互斥量方式。
选择“无锁”方式,编译、运行程序可以得到正确的结果“count=200000”,这和使用互斥量方式得到的结果一样。如果感兴趣的话,可以试试不采用任何一种同步方案(即,注释掉SYNC宏的定义),可以发现输出的结果是不正确的,道理很显然。
结果分析那么,为什么不用phtread_mutex_lock也可以实现线程间同步?可以看到程序中使用了__sync_fetch_and_add在实现加法运算。__sync_fetch_and_add是GCC内建的原子操作,它的原理《GCC内建的原子操作》中已经做了简单的叙述。如果关注GCC是如何实现该原子操作的,可以通过生成汇编代码的方式来探究。
gcc -S gcc_sync_test.c生成汇编代码gcc_sync_test.s。查看它,可以发现其中有如下代码:
jmp .L2 .L3: lock addl $1, count(%rip) addl $1, -4(%rbp) .L2: cmpl $19999, -4(%rbp)
其中,“lock addl $1, count(%rip)”中的lock既是关键所在。lock是一个指令前缀,Intel的手册上对其的解释是:
Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted.
如果不使用原子操作__sync_fetch_and_add,直接进行count++的话,产生的汇编代码大致是这样的:jmp .L2 .L3: movl count(%rip), %eax addl $1, %eax movl %eax, count(%rip) addl $1, -4(%rbp) .L2: cmpl $19999, -4(%rbp)显然缺了lock指令前缀。
至于,为什么count变量要是volatile的,这是避免使用gcc优化选项后直接将M此循环的结果算出,影响了实例代码的显著性。读者可以自己尝试一下:去掉volatile修饰,gcc编译时使用-O2优化,不使用任何同步的情况下(不启用SYNC宏),似乎也能得到正确的结果。
作者:范军 (Frank Fan) 新浪微博:@frankfan7 微信:frankfan7
Network teaming 这个概念在物理服务器中早就很普遍,我们往往会在物理服务器设置多个物理网卡的Teaming,除了防范因为网卡故障造成的单点故障之外,还有负载均衡的目的。
在虚拟环境中,绝大多数情况下无需为了容错或者负载均衡的目的,为一个虚拟机连接多个虚拟网卡。因为容错或者负载均衡的任务交付给虚拟交换机和其连接的多个物理网卡了。怎么来实现呢?这就需要在设置虚拟交换机上设置NetworkTeaming Policy。
五种策略选择中哪一种才适合你的环境?
Route based on originating virtual port
Route based on IP Hash (only one supported withStatic Etherchannel and Static 802.3ad)
Route based on Source MAC address
Route based on physical NIC load (Load BasedTeaming or LBT)
Use explicit failover order (Not a load balancingalgorithm)
除了Route based on IP Hash需要在物理交换机上设置Link Aggregation之外,其他的策略无需物理交换机上的特别设置。
情景一:
某小型公司因为成本的考虑,没有购买Enterprise Plus许可,所以使用vSphere Standard Switch。
建议:
采用Route based on originating virtual port
依据该VM连接在vSwitch的Port ID来决定把数据包传输到对应的物理网卡,快捷简单,无需VMKernel对数据包作任何多余的处理。
情景二:
某大型公司人员众多,使用vSphere Distributed Switch. ESXi连接了六张1G物理网卡。有几个虚拟机作为公司的文件服务器,经常有员工抱怨访问文件服务器延时严重,有时一个文件很长时间打不开。
建议:
采用Route based on IP Hash (only onesupported with Static Etherchannel and Static 802.3ad)。但前提是物理交换机必须支持staticEtherchannel 或者static 802.3ad link aggregation并作相应设置。
这种策略可以最大程度上提高文件服务器的吞吐量,因为Etherchannel可以把多张物理网卡绑定为一个Channel,那么吞吐量就由原来的1G变为1G* Channel内的网卡数目
以上的举例是一种比较特别的情况,同时有多个客户端对文件服务器虚拟机发起文件访问,而且1G网卡的吞吐量不能满足需求。
如果你的应用大多数情况下是点对点的通讯,这意味着同一时间内仅能用到一个物理网卡,相比LBT而言,Routebased on IP Hash并不能带来特别的好处。
情景三:
某公司使用vSphere Distributed Switch. ESXi连接了两张10G物理网卡。因为已经采用了LVS或者硬件设备实现了负载均衡,目前的虚拟环境中没有对网络吞吐量要求特别高的虚拟机。
建议:
采用Route based on physical NIC load (Load Based Teaming orLBT)
vSphere4.1 以后vDS支持该策略。连接在ESXi上某个物理网卡的使用超过了75%之后,数据包会自动发送到其他比较空闲的物理网卡,从而达到负载均衡的目的。注意虽然你设置了多个物理网卡,但某一个时刻数据包只通过某个特定的物理网卡。10G是该VM能获得的最大的吞吐量。
如果使用vDS,绝大多数情况下LBT是最佳选择。无需情景二中物理交换机的复杂配置。
参考:
NICteaming in ESXi/ESX(1004088)