1)装载:查找并装载类型的二进制数据 2)连接:执行验证,准备,和解析(可选) a) 验证:确保导入类型正确 b) 准备:为类变量分配内存,并将其初始化为默认值 c) 解析:把类型中的符号引用转换成直接引用 3)初始化:把类变量初始化为默认初值 随着Java虚拟机装载了一个类,并执行了一些它选择进行的验证之后,类就可以进入准备阶 段了。在准备阶段,Java虚拟机为类变量分配内存,设置默认初始值:但在到达初始化阶段之前, 类变量都没有被初始化为真正的初始值。(在准备阶段是不会执行Java代码的。)在准备阶段,虚 拟机把给类变量新分配的内存根据类型设置为默认值。
为了准备让一个类或者接口被"首次主动"使用,最后一个步骤就是初始化,也就是为类变量 赋予正确的初始值。这里的”正确”初始值指的是程序员希望这个类变量所具备的起始值。正 确的初始值是和在准备阶段赋予的默认初始值对比而言的。前面说过,根据类型的不同,类变 量已经被赋予了默认初始值。而正确的初始值是根据程序员制定的主观计划面生成的。
在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化语句给出的。 1)一个类变量初始化语句是变量声明后面的等号和表达式: 2)静态初始化语句是一个以static开头的程序块 example : public class Example1 { // 类变量初始化语句 static int value = (int) (Math.random()*6.0); // 静态初始化语句 static{ System.out.println("this is example"); } } 所有的类变量初始化语句和类型的静态初始化器都被Java编译器收集在—起,放到——个特殊 的方法中。对于类来说,这个方法被称作类初始化方法;对于接口来说,它被称为接口初始化 方法。在类和接口的Javaclass文件中,这个方法被称为”<clinit>”。通常的Java程序方法是无法 调用这个<clinit>方法的。这种方法只能被Java虚拟机调用
clinit>()方法 前面说过,Java编译器把类变量初始化语句和静态初始化浯句的代码都放到class文件的 <clinit>()方法中,顺序就按照它们在类或者接门声明中出现的顺序。 example: public class Example1 { static int width; static int height = (int) (Math.random()*6.0);
static{ width = (int) (Math.random()*3.0); } } java 编译器生成下面<clinit>方法: 0 invokestatic java.lang.Math.random 3 ldc2_w 6.0 (double) 6 dmul 7 d2i 8 putstatic Example1.height 11 invokestatic java.lang.Math.random 14 ldc2_w 3.0 (double) 17 dmul 18 d2i 19 putstatic Example1.width 22 return
clinit 方法首先执行唯一的类变量初始化语句初始化heght,然后在静态初始化语句中 初始化width(虽然它声明在height之前,但那仅仅是声明了类变量而不是类变量初始化语句).
除接口以外,初始化一个类之前必须保证其直接超类已被初始化,并且该初始化过程是由 Jvm 保证线程安全的。 另外,并非所有的类都会拥有一个 <clinit>() 方法。 1)如果类没有声明任何类变量,也没有静态初始化语句,那么它不会有<clinit>()方法。 2)如果声明了类变量但是没有使用类变量初始化语句或者静态初始化语句初始它们,那么类不会有<clinit>()方法。 example: public class example{ static int val; } 3)如果类仅包含静态 final 变量的类变量初始化语句,并且类变量初始化语句是编译时常量表达式,类不会有<clinit>()方法。 example: public class Example { static final String str ="abc"; static final int value = 100; } 这种情况java编译器把 str 和 value 被看做是常量,jvm会直接使用该类的常量池或者在字节码中直接存放常量值。该类不会被加载。 如果接口不包含在编译时解析成常量的字段初始化语句,接口中就包含一个<clinit>()方法。 example: interface Example{ int i =5; int hoursOfSleep = (int) (Math.random()*3.0); } 字段hoursOfSleep会被放在<clinit>()方法中(比较诡异???它被看作类变量了),而字段i被看作是编译时常量特殊处理(JAVA语法规定,接口中的变量默认自动隐含是public static final)。 java 编译器生成下面<clinit>方法: 0 invokestatic java.lang.Math.random 3 ldc2_w 3.0 (double) 6 dmul 7 d2i 8 putstatic Example.hoursOfSleep 11 return 主动使用和被动使用 在前面讲过,Java虚拟机在首次主动使用类型时初始化它们。只有6种活动被认为是主动使 用: 1)创建类的新实例, 2)调用类中声明的静态方法, 3)操作类或者接口中声明的非常量静态字段, 4)调用JavaAPI中特定的反射方法 5)初始化一个类的子类; 6)以及指定一个类作为Java虚拟机启动时的初始化类。 使用一个非常量的静态字段只有当类或者接口的确声明了这个字段时才是主动使用、比如, 类中声明的字段可能会被子类引用;接口中声明的字段可能会被子接口或者实现了这个接口的 类引用。对于子类、子接口和实现接口的类来说.这就是被动使用(使用它们并不会触发 它们的初始化)。下面的例子说明了这个原理:
class NewParement{ static int hoursOfSleep = (int) (Math.random()*3.0); static{ System.out.println("new parement is initialized."); } }
class NewbornBaby extends NewParement{ static int hoursOfCry = (int) (Math.random()*2.0); static{ System.out.println("new bornBaby is initialized."); } }
public class Example1 { public static void main(String[] args){ int hours = NewbornBaby.hoursOfSleep; System.out.println(hours); } static{ System.out.println("example1 is initialized."); } } 运行结果: example1 is initialized. new parement is initialized. 0 NewbornBaby 没有被初始化,也没有被加载。
对象的生命周期 当java虚拟机创建一个新的类实例时不管明确的还是隐含的,首先要在堆中为保存对象的实例变量分配内存,包含所有在对象类中和它超类中 声明的变量(包括隐藏的实例变量)都要分配内存。其次赋默认初值,最后赋予正确的初始值。
java编译器为每个类都至少生成一个实例初始化方法 "<init>()"与构造方法相对应。
如果构造方法调用同一个类中的另一个构造方法(构造方法重载),它对应的init<>(): 1)一个同类init<>()调用。 2)对应构造方法体代码的调用。 如果构造方法不是通过this()调用开始,且对象不是Object 它对应的init<>(): 1)一个超类init<>()调用。 2)任意实例变量初始化代码调用。 3)对应构造方法体代码的调用。 如果上述对象是Object,则去掉第一条。如果构造方法明确使用super()首先调用对应超类init<>()其余不变。 下面的例子详细说明了实例变量初始化(摘自Java Language Specification) class Point{ int x,y; Point(){x=1;y=1;} } class ColoredPoint extends Point{ int color = OxFF00FF; } class Test{ public static void main(String[] args){ ColoredPoint cp = new ColoredPoint(); System.out.println(cp.color); } } 首先,为新的ColoredPoint实例分配内存空间,以存储实例变量x,y和color;然后将这些变量初始化成默认值 在这个例子中都是0。 接下来调用无参数的ColoredPoint(),由于ColorPoint没有声明构造方法,java编译器会自动提供如下的构造方 法:ColoredPoint(){super();}。 该构造方法然后调用无参数的Point(),而Point()没有显示的超类,编译器会提供一个对其无参数的构造方法的 隐式调用:Point(){super();x=1;y=1}。 因此将会调用到Object();Object类没有超类,至此递归调用会终止。接下来会调用Object任何实例初始化语句 及任何实例变量初始化语句。 接着执行Object()由于Object类中未声明这样的构造方法。因此编译器会提供默认的构造方法object(){}。 但是执行该构造方法不会产生任何影响,然后返回。 接下来执行Point类实例变量初始化语句。当这个过程发生时,x,y的声明没有提供任何初始化表达式,因此这个 步骤未采取任何动作(x,y 仍为0); 接下来执行Point构造方法体,将x,y赋值为1。 接下来会执行类ColoredPoint的实例变量初始化语句。把color赋值0xFF00FF,最后执行ColoredPoint构造方法体 余下的部分(super()调用之后的部分),碰巧没有任何语句,因此不需要进一步的动作,初始化完成。
与C++不同的是,在创建新的类实例期间,java编程语言不会为方法分派来指定变更的规则。如果调用的方法在被 初始化对象的子类中重写,那么就是用重写的方法。甚至新对象被完全初始化前也是如此。编译和运行下面的例子 class Super{ Super(){printThree();} void printThree{System.out.println("Three");} } class Test extends Super{ int three = (int)Math.PI; // That is 3 public static void main(String args[]){ Test t = new Test(); t.printThree(); } void printThree(){System.out.println(three);} } 输出: 0 3 这表明Super类中的printThree()没有被执行。而是调用的Test中的printThree()。
posted @ 2010-07-14 16:18 AK47 阅读(890) | 评论 (0) | 编辑 收藏
Java虚拟机体系结构
方法区 在Java虚拟机中,被装载类型的信息存储在一个逻辑上被称为方法区的内存中。 当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,-->读入这个class文件(一个线性的二进制流)->将它传入虚拟机--> 虚拟机提取类型信息,并将信息存入方法区,类型中的类(静态)变量也存储在方法区. 方法区特点: 1)所有线程共享方法区。它是线程安全的。 2)方法区大小不是固定的。虚拟机根据需要自行调整。 3)方法区可以被垃圾回收。 对于每个被装载的类型,虚拟机会在方法区中存储以下信息。 1)类型的基本信息; a)类型的全限定名 b)类型的直接超类全限定名(除非这个类型是java.lang.Objet,它没超类)。 c)类型是类类型还是接口类型(就是说是一个类还是一个接口)。 d)类型的访问修饰符(public ,abstract或final的某个子类) e)任何直接超接口的全限定名的有序列表。 2)该类型的常量池 虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合, 包括直接常量(string,integer,floating point常量)和对其他类型、字段和方法的符号引用。 池中的数据项就像数组一样是通过索引访问的。因为常量池存储了相应类型所用到的所有类型、 字段和方法的符号引用,所以它在Java程序的动态连接中起着核心的作用。 3)字段信息 类型中声明的每一个字段,方法区中必须保存下面的信息,字段在类或接口中声明的顺序也必须保存。 字段名,字段类型,字段修饰符(public private protected static final 等) 4)方法信息 类型中声明的每一个方法,方法区中必须保存下面的信息,方法在类或接口中声明的顺序也必须保存。 方法名,返回值类型,参数数量和类型(按声明顺序),方法修饰符(public private protected static final 等) 如果方法不是抽象的或本地的还必须保存:方法字节码,操作数栈和该方法在栈针中局部变量的大小,异常表。 5)除了常量以外的所有类(静态)变量 这里主要说下编译时常量:就是那些用final声明以及编译时已知的值初始化的类变量(例如:static final int val =5) 每个编译时常量的类型都会复制它所有常量到它自己的常量池中或者它的字节码流中(通常情况下编译时直接替换字节码)。 6)一个到类classLoader的引用 指向ClassLoader类的引用 每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器 还是由用户自定义类装载器装载的。如果是用户自定义类装载器装载的,那么虚拟机必须在类 型信息中存储对该装载器的引用:这是作为方法表中的类型数据的一部分保存的。 虚拟机会在动态连按期间使用这个信息。当某个类型引用另一个类型的时候,虚拟机会请求装载 发起引用类型的类装载器来装载被引用的类型。这个动态连接的过程,对于虚拟机分离命名空间 的方式也是至关重要的。为了能够正确地执行动态连接以及维护多个命名空间,虚拟机需要在方 法表中得知每个类都是由哪个类装载器装载的。 7)一个到Class类的引用 指向Class类的引用 对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为 它创建一个java.lang.Class类的实例(Class实例放在内存中的堆区),而且虚拟机还必须以某种方式把这个实例的引用存储在方法区 为了尽可能提高访问效率,设计者必须仔细设计存储在方法区中的类型信息的数据结构,因此, 除了以上时论的原始类型信息,实现中还可能包括其他数据结构以加快访问原始数据的速度,比如方法表。 虚拟机对每个装载的非抽象类,都生成一个方法表,把它作为类信息的一部分保存在方法区。方法表是一个数组, 它的元素是所有它的实例可能被调用的实例方法的直接引用,包括那些从超类继承过来的实例方法:(对于抽象类和接口,方法表没有什么帮 助,因为程序决不会生成它们的实例。)运行时可以通过方法表快速搜寻在对象中调用的实例方法。 方法区使用的例子
class Lava{ private int speed = 5; void flow(){ } }
public class Volcano { public static void main(String args[]){ Lava lava = new Lava(); lava.flow(); } }
1)虚拟机在方法区查找Volcano这个名字,未果,载入volcano.class文件,并提取相应信息 存入方法区。 2)虚拟机开始执行Volcano类中main()方法的字节码的时候,尽管Lava类还没被装载, 但是和大多数(也许所有)虚拟机实现一样,它不会等到把程序中用到的所有类都装载后才开 始运行程序。恰好相反,它只在需要时才装载相应的类。 3)main()的第一条指令告知虚拟机为列在常量池第一项的类分配足够的内存。所以虚拟机 使用指向Volcano常量池的指针找到第一项,发现它是一个对Lava类的符号引用,然后它就检查 方法区,看Lava类是否已经被装载了。 4)当虚拟机发现还没有装载过名为"Lava"的类时,它就开始查找并装载文件“Lava.class”, 并把从读入的二进制数据中提取的类型信息放在方法区中。 5)虚拟机以一个直接指向方法区Lava类数据的指针来替换常量池第—项(就是那个 字符串“Lava”)——以后就可以用这个指针来快速地访问Lava类了。这个替换过程称为常量池 解析,即把常量池中的符号引用替换为直接引用:这是通过在方法区中搜索被引用的元素实现 的,在这期间可能又需要装载其他类。在这里,我们替换掉符号引用的“直接引用”是一个本 地指针。 6)虚拟机准备为一个新的Lava对象分配内存。此时,它又需要方法区中的信息。还记 得刚刚放到Volcano类常量池第——项的指针吗?现在虚拟机用它来访问Lava类型信息(此前刚放 到方法区中的),找出其中记录的这样一个信息:一个Lava对象需要分配多少堆空间。 7)虚拟机确定一个Lava对象大小后,就在堆上分配空间,并把这个对象实例变量speed初始化为默认初始值0 8)当把新生成的Lava对象的引用压到栈中,main()方法的第一条指令也完成了,指令通过这个引用 调用Java代码(该代码把speed变量初始化为正确初始值5).另外用这个引用调用Lava对象引用的flow()方法。
堆 每个java虚拟机实例都有一个方法区以及一个堆,一个java程序独占一个java虚拟机实例,而每个java程序都有自己的堆空间,它们不会彼此干扰,但同一个java程序的多个线程共享一个堆空间。这种情况下要考虑多线程访问同步问题。 Java栈 一个新线程被创建时,都会得到自己的PC寄存器和一个java栈,虚拟机为每个线程开辟内存区。这些内存区是私有的,任何线程不能访问其他线程的PC寄存器和java栈。java栈总是存储该线程中java方法的调用状态。包括它的局部变量,被调用时传进来的参数,它的返回值,以及运算的中间结果等。java栈是由许多栈帧或者说帧组成,一个栈帧包含一个java方法的调用状态,当线程调用java方法时,虚拟机压入一个新的栈帧到该线程的java栈中。当方法返回时,这个栈帧被从java栈中弹出并抛弃。 .本地方法栈 任何本地方法接口都会使用某种本地方法饯。当线程调用Java方法时,虚拟机会创建一个新的栈帧井压人Java栈。 然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压人新的帧,虚拟机只是简单地动态连接 并直接调用指定的本地方法。可以把这看做是虚拟机利用本地方法来动态扩展自己。
posted @ 2010-07-06 13:47 AK47 阅读(371) | 评论 (0) | 编辑 收藏
posted @ 2010-06-10 14:17 AK47 阅读(446) | 评论 (0) | 编辑 收藏
这两天,突然无法启动我的MyEclipse6.5了,不知道为什么,提示错误: JVM terminated. Exit code=-1。
昨天,我以为是机器运行时间太长,重启一下,果然好了。但是今天又来了。看了一下错误提示,我以为是JVM有问题,就在启动Eclipse里加个JVM的参数,结果还是不行。
后来在网上找了一下,有人说是JAVA环境配置的问题,我想这不可能,因为以前一直用的好好的。有人说是JVM的问题,这个我刚刚换了一个,也不是这个问题,后来看来有人说是:eclipse.ini中内存设置过大的问题,虽然我不以为然,还是试了一下,以前我修改过内存设置,一直都好好的,之前eclipse.ini的配置如下:
-showsplash com.genuitec.myeclipse.product --launcher.XXMaxPermSize 512m -vmargs -Xms256m -Xmx512m -Duser.language=en -XX:PermSize=256M -XX:MaxPermSize=512M
现在修改了一下,-Xms256m改成-Xms128m,把Xmx512m 改为 Xmx256m,结果还真的好了,没想到居然是这样的小问题引起来的。
posted @ 2010-03-05 12:10 AK47 阅读(253) | 评论 (0) | 编辑 收藏
这样学习设计模式肯定便于理解: http://hi.baidu.com/xghzlg/blog/item/3288de589071d7202934f06f.html
从追MM谈Java的23种设计模式 设计模式做为程序员的“内功心法”,越来越受到.net 社区的重视,这种变化是很可喜的,Java社区走在了我们的前面,但这种状况也许有一天会发生改变。
从追MM谈Java的23种设计模式
1、FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯 德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory.
工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点 是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。
程序代码
以下是引用片段:
以下是引用片段: public class Factory{ public String Boy = "boy" ; public String Girl = "girl" ; public People getPeople (String people){ if (people.equals("boy")){ return new Boy(); }else if(people.equals("girl")){ return new Girl(); } } }
2、BUILDER—MM最爱听的就是“我爱你”这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见到MM我只要按对应的键,它就能够用相应的语言说出“我爱你”这句话了,国外的MM也可以轻松搞掂,这就是我的“我爱你”builder。(这一定比美军在伊拉克用的翻译机好卖)
建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得 产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。
3、FACTORY METHOD—请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模 式,带着MM到服务员那儿,说“要一个汉堡”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。
工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出 具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
4、PROTOTYPE—跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是 我的情话prototype了。(100块钱一份,你要不要)
原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
5、SINGLETON—俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个 人,那就是我(刚才做了个梦啦,哪有这么好的事)
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的 “单一实例”的需求时才可使用。
以下是引用片段: public class SingLeton{ private static SingLeton instance = new SingLeton(); public static SingLeton getInstance(){ return instance; } }
6、ADAPTER—在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他 作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了(也不知道他会不会耍我)
适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类 能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
7、BRIDGE—早上碰到MM,要说早上好,晚上碰到MM,要说晚上好; 碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型, 要说你的头发好漂亮哦。不要问我“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了
桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的 抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
8、COMPOSITE—Mary今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite模式了,你会了没有?
合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成 模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
9、DECORATOR—Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?
装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个 对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。
10、FA?ADE—我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Fa?ade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。
门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用 。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。
11、FLYWEIGHT—每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。
享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。
12、PROXY—跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序 做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。
代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
以下是引用片段: public interface FactoryProxy{ public People createBoy(); public People creteGirl(); }
13、CHAIN OF RESPONSIBLEITY—晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上 “Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接
起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。
14、COMMAND—俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送 COMMAND,就数你最小气,才请我吃面。”,
命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。
15、INTERPRETER—俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只 要做一个Interpreter,照着上面的脚本执行就可以了。
解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。
16、ITERATOR—我爱上了Mary,不顾一切的向她求婚。
Mary:“想要我跟你结婚,得答应我的条件”
我:“什么条件我都答应,你说吧”
Mary:“我看上了那个一克拉的钻石”
我:“我买,我买,还有吗?”
Mary:“我看上了湖边的那栋别墅”
Mary:“我看上那辆法拉利跑车”
我脑袋嗡的一声,坐在椅子上,一咬牙:“我买,我买,还有吗?”
……
迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色 变化。
17、MEDIATOR—四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这 里拿,赔了钱的也付给我,一切就OK啦,俺得到了四个MM的电话。
调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
18、MEMENTO—同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与 哪个MM说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。
备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一 个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
19、OBSERVER—想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知 我们,直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦
观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生 变化时,会通知所有观察者对象,使他们能够自动更新自己。
20、STATE—跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的 MM就会说“有事情啦”,对你不讨厌但还没喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。
状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子 类。
21、STRATEGY—跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目 的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。
策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。
22、TEMPLATE METHOD——看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现);
模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
23、VISITOR—情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;
访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
posted @ 2010-01-04 16:48 AK47 阅读(243) | 评论 (0) | 编辑 收藏
看了下面的文章才彻底明白了ThreadLocal 与 getCurrentSession的关系 http://hi.baidu.com/%B7%C7%D4%C2%CE%DE%D0%C4/blog/item/8b14b8db49b40961d1164e54.html
1 getCurrentSession创建的session会和绑定到当前线程,而openSession不会。
2 getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭
这里getCurrentSession本地事务(本地事务:jdbc)时 要在配置文件里进行如下设置
* 如果使用的是本地事务(jdbc事务) <property name="hibernate.current_session_context_class">thread</property> * 如果使用的是全局事务(jta事务) <property name="hibernate.current_session_context_class">jta</property>
getCurrentSession () 使用当前的session openSession() 重新建立一个新的session
在一个应用程序中,如果DAO 层使用Spring 的hibernate 模板,通过Spring 来控制session 的生命周期,则首选getCurrentSession ()。
使用Hibernate的大多数应用程序需要某种形式的“上下文相关的” session,特定的session在整个特定的上下文范围内始终有效。然而,对不同类型的应用程序而言,要为什么是组成这种“上下文”下一个定义通常是困难的;不同的上下文对“当前”这个概念定义了不同的范围。在3.0版本之前,使用Hibernate的程序要么采用自行编写的基于 ThreadLocal的上下文session,要么采用HibernateUtil这样的辅助类,要么采用第三方框架(比如Spring或Pico),它们提供了基于代理(proxy)或者基于拦截器(interception)的上下文相关session。从3.0.1版本开始,Hibernate增加了SessionFactory.getCurrentSession()方法。一开始,它假定了采用JTA事务,JTA事务定义了当前session的范围和上下文(scope and context)。Hibernate开发团队坚信,因为有好几个独立的JTA TransactionManager实现稳定可用,不论是否被部署到一个J2EE容器中,大多数(假若不是所有的)应用程序都应该采用JTA事务管理。基于这一点,采用JTA的上下文相关session可以满足你一切需要。
更好的是,从3.1开始,SessionFactory.getCurrentSession()的后台实现是可拔插的。因此,我们引入了新的扩展接口 (org.hibernate.context.CurrentSessionContext)和新的配置参数 (hibernate.current_session_context_class),以便对什么是“当前session”的范围和上下文(scope and context)的定义进行拔插。
请参阅 org.hibernate.context.CurrentSessionContext接口的Javadoc,那里有关于它的契约的详细讨论。它定义了单一的方法,currentSession(),特定的实现用它来负责跟踪当前的上下文session。Hibernate内置了此接口的两种实现。
org.hibernate.context.JTASessionContext - 当前session根据JTA来跟踪和界定。这和以前的仅支持JTA的方法是完全一样的。详情请参阅Javadoc。
org.hibernate.context.ThreadLocalSessionContext - 当前session通过当前执行的线程来跟踪和界定。详情也请参阅Javadoc。
这两种实现都提供了“每数据库事务对应一个session”的编程模型,也称作每次请求一个session。Hibernate session的起始和终结由数据库事务的生存来控制。假若你采用自行编写代码来管理事务(比如,在纯粹的J2SE,或者 JTA/UserTransaction/BMT),建议你使用Hibernate Transaction API来把底层事务实现从你的代码中隐藏掉。如果你在支持CMT的EJB容器中执行,事务边界是声明式定义的,你不需要在代码中进行任何事务或 session管理操作。请参阅第 11 章 事务和并发一节来阅读更多的内容和示例代码。
hibernate.current_session_context_class 配置参数定义了应该采用哪个org.hibernate.context.CurrentSessionContext实现。注意,为了向下兼容,如果未配置此参数,但是存在org.hibernate.transaction.TransactionManagerLookup的配置,Hibernate会采用org.hibernate.context.JTASessionContext。一般而言,此参数的值指明了要使用的实现类的全名,但那两个内置的实现可以使用简写,即"jta"和"thread"。
1、getCurrentSession()与openSession()的区别?
* 采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession() 创建的session则不会 * 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession() 创建的session必须手动关闭 2、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置: * 如果使用的是本地事务(jdbc事务) <property name="hibernate.current_session_context_class">thread</property> * 如果使用的是全局事务(jta事务) <property name="hibernate.current_session_context_class">jta</property>
利于ThreadLocal模式管理Session 早在Java1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序 时提供了一种新的选择。ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread, 而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal) 其实的功用非常简单,就是为每一个使用某变量的线程都提供一个该变量值的副本,是每一个线程都可以独立地改变自己的副本, 而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量。 ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map, 用于存储每一个线程的变量的副本。比如下面的示例实现(为了简单,没有考虑集合的泛型): public class HibernateUtil {
public static final ThreadLocal session =new ThreadLocal();
public static final SessionFactory sessionFactory; static { try { sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); } }
public static Session currentSession() throws HibernateException { Session s = session.get(); if(s == null) { s = sessionFactory.openSession(); session.set(s); } return s; }
public static void closeSession() throws HibernateException { Session s = session.get(); if(s != null) { s.close(); } session.set(null); } }
原来一切都是那么简单。
posted @ 2009-11-26 14:35 AK47 阅读(670) | 评论 (0) | 编辑 收藏
<?xml version="1.0" encoding='UTF-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping> <class> <!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional--> <cache usage="read-write"/> </class>
</hibernate-mapping>
posted @ 2009-11-18 14:12 AK47 阅读(221) | 评论 (0) | 编辑 收藏
事务就是一系列的操作,这些操作完成一项任务.只要这些操作里有一个操作没有成功,事务就操作失败,发生回滚事件.即撤消前面的操作,这样可以保证数据的一致性.而且可以把操作暂时放在缓存里,等所有操作都成功有提交数据库,这样保证费时的操作都是有效操作. 如果没有特殊声明,事务就是指数据库事务简单的讲就是对数据库表的添加、删除、修改和查询操作。 从编程的角度来说事务可由程序员来设置,(何时开启,何时提交,何时回滚)如果没有设置则按数据库默认自动划分事务。而事务最终在数据库上执行.所以要求数据库支持事务。
事务具有四个特征:原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持续性( Durability )。这四个特性简称为 ACID 特性。 1 、原子性 事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 2 、一致性 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。 3 、隔离性 一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。 4 、持续性 也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。 数据库系统是允许多个用户共享数据库资源,尤其是多个用户可以同时存取相同数据。(多用户同时对一个表操作也就是并发) 我们主观上虽不想这么做,可是这种情况是存在的,没有原因。而并发会破坏事务ACID特性 (隔离性,一致性)。
并发会带来下列问题: 脏读:一个事务读取了未提交的事务 不可重复读:同一个事务中多次读取同一个数据返回的结果不同 幻读:一个事务读取到了另一个事务已提交的insert数据。 如果应用程序使用完全隔离的事务,那么同时执行多个事务的效果将与串行执行(一个接一个的顺序执行)完全等效。为解决事务之间的并发带来的个问题,必须在事务之间建立隔离关系(使用隔离级别)。
事务的隔离级别:就是对事务并发控制的等级,ANSI/ISO SQL将其分为串行化(SERIALIZABLE)、可重复读(REPEATABLE READ)、读已提交(READ COMMITED)、读未提交(READ UNCOMMITED)四个等级 1 Serializable:最严格的级别,事务串行执行,资源消耗最大; 2 REPEATABLE READ:读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。 3 READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。 4 Read Uncommitted:最低的事务隔离级别,保证了读取过程中不会读取到非法数据。
共享锁:共享锁用于读取数据操作,它允许其他事务同时读取某锁定的资源,但不允许其他事务更新它。 排他锁:排它锁用于修改数据的场合。它锁定的资源,其他事务不能读取也不能修改。 更新锁:更新锁在更新操作的初始化阶段用来锁定可能要被修改的资源,从而避免使用共享锁造成的死锁现象
常见的并发控制锁
http://hahalzb.blogbus.com/logs/19150842.html 心晴怡然
乐观锁
处理并发更新的一种方式是使用乐观锁(optimistic locking)。乐观锁的工作原理是让应用程序检查它即将更新的数据是否已被另一个事务修改(自该数据上次读取以来)。实现乐观锁的一种常见做法是在每个表里添加一个版本字段,每次应用程序更新数据表记录时就增加这个版本字段。每个UPDATE语句中的WHERE子句会根据上次读取的值来判断这个版本号是否改变。通过查看PreparedStatement.executeUpdate()返回的记录数,应用程序可以判断UPDATE语句是否成功。如果这条记录已被另一个事务更新或删除,应用程序可以回滚这个事务,并重新开始。 在直接执行SQL语句的应用程序中,乐观锁机制的实现非常容易。不过,使用诸如JDO和Hibernate的持久层构架时,实现更为容易,因为它们已将乐观锁作为配置选项提供。一旦启用该配置选项,持久层框架会自动生成SQL UPDATE语句,执行版本检查。第12章将分析乐观锁的使用时机及其缺点,并向你展示怎样在iBATIS、JDO和Hibernate中使用乐观锁。 乐观锁的名称源自如下假定情况,即并发更新的几率极小,此外应用程序并不阻止并发更新,而是检测并发更新,并从并发更新中恢复过来。另一种方式是使用悲观锁(pessimistic locking),它假定并发更新将会发生,因此必须预先阻止。
悲观锁
不同于乐观锁的另一种方式是使用悲观锁。当读取某些记录时,事务先锁住这些记录,这样可以防止其他事务访问这些数据记录。具体细节要视数据库而定,不过糟糕的是,并非所有数据库都支持悲观锁。如果数据库支持悲观锁,在直接执行SQL语句的应用程序中,实现悲观锁非常 容易。然而,正如你所预料的,在JDO或Hibernate应用程序中使用悲观锁更为容易。JDO以配置选项的方式提供悲观锁,而Hibernate则提供一个简单实用的API,来锁定对象。同样,在第12章,你将学习何时使用悲观锁,分析其缺点,并看看怎样在iBATIS、JDO和Hibernate中使用悲观锁。
posted @ 2009-11-11 17:23 AK47 阅读(980) | 评论 (0) | 编辑 收藏
package com.kangdy.test;
public interface UserManager { public void addUser(String userName); }
public class UserManagerImpl implements UserManager {
public void addUser(String userName) { System.out.println("用户 : "+userName+" 添加成功"); }
}
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class JDKStaticProxy implements InvocationHandler{ //目标对象索引 private Object targetObject; /* * 通过构造方法引入目标对象 */ public JDKStaticProxy(Object targetObject){ this.targetObject = targetObject; } /* * 创建代理对象 */ public Object createProxyObject(){ Object proxyObject = Proxy.newProxyInstance( this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); return proxyObject; } /* * proxyObject:代理对象 * method: 被拦截到的目标对象的method * args: 被拦截到的目标对象的method的参数 * (non-Javadoc) * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) */ public Object invoke(Object proxyObject, Method method, Object[] args) throws Throwable { //添加业务逻辑 busniessLogic(); //代理运行目标对象的method Object result = method.invoke(this.targetObject, args); return result; } /* * 添加业务逻辑,这里只是简单打印一句话。 */ private void busniessLogic(){ System.out.println("这是代理方法"); } }
代理类我添加很多注释。应该很清楚了。这里我简单说一下流程:当代理对象被调用的时候先会执行invoke方法,在此方法里面我们可添加 自己的业务逻辑代码,然后才会执行目标对象的真实方法:method.invoke(this.targetObject, args);目标对象方法可能会有返回值,在这 里当存在返回值的时候我们返回一个Object.
下面代码是客户端调用和调用结果:
import org.junit.Test;
public class TestJDKStaticProxy { @Test public void testJDKStaticProxy(){ JDKStaticProxy proxy = new JDKStaticProxy(new UserManagerImpl()); UserManager userManager = (UserManager) proxy.createProxyObject(); userManager.addUser("张三"); } }
posted @ 2009-11-05 16:06 AK47 阅读(985) | 评论 (0) | 编辑 收藏
以前做过Structs 的项目,可是一直没做太深的研究,尤其是关于线程安全的 在网上搜了一下很多这方面的资料,引用了一些,总结了一下:
这篇文章对什么是线程安全的代码和如何使用线程安全的代码做了详细阐述 http://hi.baidu.com/niujunkai/blog/item/021964adc130660a4a36d6ab.html 下面是它内容的引用:
1.什么是线程安全的代码 在多线程环境下能正确执行的代码就是线程安全的。 安全的意思是能正确执行,否则后果是程序执行错误,可能出现各种异常情况。
2.如何编写线程安全的代码 很多书籍里都详细讲解了如何这方面的问题,他们主要讲解的是如何同步线程对共享资源的使用的问题。主要是对synchronized关键字的各种用法,以及锁的概念。Java1.5中也提供了如读写锁这类的工具类。这些都需要较高的技巧,而且相对难于调试。
但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。 我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下: 1)常量始终是线程安全的,因为只存在读操作。 2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。 3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。 struts user guide里有: Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class. 译:只使用用局部变量。--编写线程安全的代码最重要的原则就是,在Action类中只使用局部变量,不使用实例变量。
总结: 在Java的Web服务器环境下开发,要注意线程安全的问题。最简单的实现方式就是在Servlet和Struts Action里不要使用类变量、实例变量,但可以使用类常量和实例常量。如果有这些变量,可以将它们转换为方法的参数传入,以消除它们。 注意一个容易混淆的地方:被Servlet或Action调用的类中(如值对象、领域模型类)中是否可以安全的使用实例变量?如果你在每次方法调用时 新建一个对 象,再调用它们的方法,则不存在同步问题---因为它们不是多个线程共享的资源,只有共享的资源才需要同步---而Servlet和Action的实例对于多个线程是共享 的。 换句话说,Servlet和Action的实例会被多个线程同时调用,而过了这一层,如果在你自己的代码中没有另外启动线程,且每次调用后续业务对象时都是先 新建一个实例再调用,则都是线程安全的。
如果想加深理解servlet的多线程可以读读此文,阐述的很详细。 http://hi.baidu.com/platon/blog/item/64a20ff3f96e7fce0b46e031.html
posted @ 2009-10-29 16:26 AK47 阅读(1029) | 评论 (0) | 编辑 收藏
Powered by: BlogJava Copyright © AK47