java總結(基礎知識-面試)

lJava發射(案例)php

l反射含義:html

lJAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。java

lJAVA反射(放射)機制程序運行時,容許改變程序結構或變量類型,這種語言稱爲動態語言。從這個觀點看,PerlPythonRuby是動態語言,C++JavaC#不是動態語言。可是JAVA有着一個很是突出的動態相關機制:Reflection,用在Java身上指的是咱們能夠於運行時加載、探知、使用編譯期間徹底未知的classes。換句話說,Java程序能夠加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對象實體、或對其fields設值、或喚起其methodsnode

l【案例1】經過一個對象得到完整的包名和類名c++

l1程序員

l2web

l3面試

l4算法

l5spring

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

lpackageReflect;

 

l/**

l* 經過一個對象得到完整的包名和類名

l* */

lclassDemo{

l//other codes...

l}

 

lclasshello{

lpublicstaticvoidmain(String[] args) {

lDemo demo=newDemo();

lSystem.out.println(demo.getClass().getName());

l}

l}

l【運行結果】:Reflect.Demo

l添加一句:全部類的對象其實都是Class的實例。

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

lpackageReflect;

lclassDemo{

l//other codes...

l}

 

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo1=null;

lClass<?> demo2=null;

lClass<?> demo3=null;

ltry{

l//通常儘可能採用這種形式

ldemo1=Class.forName("Reflect.Demo");

l}catch(Exception e){

le.printStackTrace();

l}

ldemo2=newDemo().getClass();

ldemo3=Demo.class;

 

lSystem.out.println("類名稱 "+demo1.getName());

lSystem.out.println("類名稱 "+demo2.getName());

lSystem.out.println("類名稱 "+demo3.getName());

 

l}

l}

l【案例2】實例化Class類對象

l【運行結果】:

l類名稱 Reflect.Demo

l類名稱 Reflect.Demo

l類名稱 Reflect.Demo

l【案例3】經過Class實例化其餘類的對象

l經過無參構造實例化對象

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l42

l43

l44

l45

l46

l47

lpackageReflect;

 

lclassPerson{

 

lpublicString getName() {

lreturnname;

l}

lpublicvoidsetName(String name) {

lthis.name = name;

l}

lpublicintgetAge() {

lreturnage;

l}

lpublicvoidsetAge(intage) {

lthis.age = age;

l}

l@Override

lpublicString toString(){

lreturn"["+this.name+" "+this.age+"]";

l}

lprivateString name;

lprivateintage;

l}

 

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lPerson per=null;

ltry{

lper=(Person)demo.newInstance();

l}catch(InstantiationException e) {

l// TODO Auto-generated catch block

le.printStackTrace();

l}catch(IllegalAccessException e) {

l// TODO Auto-generated catch block

le.printStackTrace();

l}

lper.setName("Rollen");

lper.setAge(20);

lSystem.out.println(per);

l}

l}

l【運行結果】:

l[Rollen 20]

l可是注意一下,當咱們把Person中的默認的無參構造函數取消的時候,好比本身定義只定義一個有參數的構造函數以後,會出現錯誤:

l好比我定義了一個構造函數:

l1

l2

l3

l4

lpublicPerson(String name,intage) {

lthis.age=age;

lthis.name=name;

l}

l而後繼續運行上面的程序,會出現:

ljava.lang.InstantiationException: Reflect.Person

lat java.lang.Class.newInstance0(Class.java:340)

lat java.lang.Class.newInstance(Class.java:308)

lat Reflect.hello.main(hello.java:39)

lException in thread "main"java.lang.NullPointerException

lat Reflect.hello.main(hello.java:47)

l因此你們之後再編寫使用Class實例化其餘類的對象的時候,必定要本身定義無參的構造函數

 

l【案例3】經過Class調用其餘類中的構造函數(也能夠經過這種方式經過Class建立其餘類的對象)

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l42

l43

l44

l45

l46

l47

l48

l49

l50

l51

l52

l53

l54

l55

l56

l57

l58

l59

l60

l61

lpackageReflect;

 

limportjava.lang.reflect.Constructor;

 

lclassPerson{

 

lpublicPerson() {

 

l}

lpublicPerson(String name){

lthis.name=name;

l}

lpublicPerson(intage){

lthis.age=age;

l}

lpublicPerson(String name,intage) {

lthis.age=age;

lthis.name=name;

l}

lpublicString getName() {

lreturnname;

l}

lpublicintgetAge() {

lreturnage;

l}

l@Override

lpublicString toString(){

lreturn"["+this.name+" "+this.age+"]";

l}

lprivateString name;

lprivateintage;

l}

 

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lPerson per1=null;

lPerson per2=null;

lPerson per3=null;

lPerson per4=null;

l//取得所有的構造函數

lConstructor<?> cons[]=demo.getConstructors();

ltry{

lper1=(Person)cons[0].newInstance();

lper2=(Person)cons[1].newInstance("Rollen");

lper3=(Person)cons[2].newInstance(20);

lper4=(Person)cons[3].newInstance("Rollen",20);

l}catch(Exception e){

le.printStackTrace();

l}

lSystem.out.println(per1);

lSystem.out.println(per2);

lSystem.out.println(per3);

lSystem.out.println(per4);

l}

l}

l【運行結果】:

l[null 0]

l[Rollen 0]

l[null 20]

l[Rollen 20]

l【案例】

l返回一個類實現的接口:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l42

l43

l44

l45

l46

l47

l48

lpackageReflect;

 

linterfaceChina{

lpublicstaticfinalString name="Rollen";

lpublicstaticintage=20;

lpublicvoidsayChina();

lpublicvoidsayHello(String name,intage);

l}

 

lclassPersonimplementsChina{

lpublicPerson() {

 

l}

lpublicPerson(String sex){

lthis.sex=sex;

l}

lpublicString getSex() {

lreturnsex;

l}

lpublicvoidsetSex(String sex) {

lthis.sex = sex;

l}

l@Override

lpublicvoidsayChina(){

lSystem.out.println("hello ,china");

l}

l@Override

lpublicvoidsayHello(String name,intage){

lSystem.out.println(name+" "+age);

l}

lprivateString sex;

l}

 

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

l//保存全部的接口

lClass<?> intes[]=demo.getInterfaces();

lfor(inti =0; i < intes.length; i++) {

lSystem.out.println("實現的接口 "+intes[i].getName());

l}

l}

l}

l【運行結果】:

l實現的接口 Reflect.China

l(注意,如下幾個例子,都會用到這個例子的Person類,因此爲節省篇幅,此處再也不粘貼Person的代碼部分,只粘貼主類hello的代碼)

l【案例4】:取得其餘類中的父類

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

l//取得父類

lClass<?> temp=demo.getSuperclass();

lSystem.out.println("繼承的父類爲: "+temp.getName());

l}

l}

l【運行結果】

l繼承的父類爲: java.lang.Object

l【案例】:得到其餘類中的所有構造函數

l這個例子須要在程序開頭添加importjava.lang.reflect.*;

l而後將主類編寫爲:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lConstructor<?>cons[]=demo.getConstructors();

lfor(inti =0; i < cons.length; i++) {

lSystem.out.println("構造方法: "+cons[i]);

l}

l}

l}

l【運行結果】:

l構造方法: public Reflect.Person()

l構造方法: public Reflect.Person(java.lang.String)

l可是細心的讀者會發現,上面的構造函數沒有public或者private這一類的修飾符

l下面這個例子咱們就來獲取修飾符

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lConstructor<?>cons[]=demo.getConstructors();

lfor(inti =0; i < cons.length; i++) {

lClass<?> p[]=cons[i].getParameterTypes();

lSystem.out.print("構造方法: ");

lintmo=cons[i].getModifiers();

lSystem.out.print(Modifier.toString(mo)+" ");

lSystem.out.print(cons[i].getName());

lSystem.out.print("(");

lfor(intj=0;j<p.length;++j){

lSystem.out.print(p[j].getName()+" arg"+i);

lif(j<p.length-1){

lSystem.out.print(",");

l}

l}

lSystem.out.println("){}");

l}

l}

l}

l【運行結果】:

l構造方法: public Reflect.Person(){}

l構造方法: public Reflect.Person(java.lang.String arg1){}

l有時候一個方法可能還有異常,呵呵。下面看看:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

lclasshello{

lpublicstaticvoidmain(String[] args) {

lClass<?> demo=null;

ltry{

ldemo=Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lMethod method[]=demo.getMethods();

lfor(inti=0;i<method.length;++i){

lClass<?> returnType=method[i].getReturnType();

lClass<?> para[]=method[i].getParameterTypes();

linttemp=method[i].getModifiers();

lSystem.out.print(Modifier.toString(temp)+" ");

lSystem.out.print(returnType.getName()+" ");

lSystem.out.print(method[i].getName()+" ");

lSystem.out.print("(");

lfor(intj=0;j<para.length;++j){

lSystem.out.print(para[j].getName()+" "+"arg"+j);

lif(j<para.length-1){

lSystem.out.print(",");

l}

l}

lClass<?> exce[]=method[i].getExceptionTypes();

lif(exce.length>0){

lSystem.out.print(") throws ");

lfor(intk=0;k<exce.length;++k){

lSystem.out.print(exce[k].getName()+" ");

lif(k<exce.length-1){

lSystem.out.print(",");

l}

l}

l}else{

lSystem.out.print(")");

l}

lSystem.out.println();

l}

l}

l}

l【運行結果】:

lpublic java.lang.String getSex ()

lpublic void setSex (java.lang.String arg0)

lpublic void sayChina ()

lpublic void sayHello (java.lang.String arg0,int arg1)

lpublic final native void wait (long arg0) throwsjava.lang.InterruptedException

lpublic final void wait () throwsjava.lang.InterruptedException

lpublic final void wait (long arg0,int arg1) throwsjava.lang.InterruptedException

lpublic boolean equals (java.lang.Object arg0)

lpublic java.lang.String toString ()

lpublic native int hashCode ()

lpublic final native java.lang.Class getClass ()

lpublic final native void notify ()

lpublic final native void notifyAll ()

l【案例6接下來讓咱們取得其餘類的所有屬性吧,最後我講這些整理在一塊兒,也就是經過class取得一個類的所有框架

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

lclasshello {

lpublicstaticvoidmain(String[] args) {

lClass<?> demo =null;

ltry{

ldemo = Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

lSystem.out.println("===============本類屬性========================");

l// 取得本類的所有屬性

lField[] field = demo.getDeclaredFields();

lfor(inti =0; i < field.length; i++) {

l// 權限修飾符

lintmo = field[i].getModifiers();

lString priv = Modifier.toString(mo);

l// 屬性類型

lClass<?> type = field[i].getType();

lSystem.out.println(priv +" "+ type.getName() +" "

l+ field[i].getName() +";");

l}

lSystem.out.println("===============實現的接口或者父類的屬性========================");

l// 取得實現的接口或者父類的屬性

lField[] filed1 = demo.getFields();

lfor(intj =0; j < filed1.length; j++) {

l// 權限修飾符

lintmo = filed1[j].getModifiers();

lString priv = Modifier.toString(mo);

l// 屬性類型

lClass<?> type = filed1[j].getType();

lSystem.out.println(priv +" "+ type.getName() +" "

l+ filed1[j].getName() +";");

l}

l}

l}

l【運行結果】:

l===============本類屬性========================

lprivate java.lang.String sex;

l===============實現的接口或者父類的屬性========================

lpublic static final java.lang.String name;

lpublic static final int age;

l【案例】其實還能夠經過反射調用其餘類中的方法:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

lclasshello {

lpublicstaticvoidmain(String[] args) {

lClass<?> demo =null;

ltry{

ldemo = Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

ltry{

l//調用Person類中的sayChina方法

lMethod method=demo.getMethod("sayChina");

lmethod.invoke(demo.newInstance());

l//調用Person的sayHello方法

lmethod=demo.getMethod("sayHello", String.class,int.class);

lmethod.invoke(demo.newInstance(),"Rollen",20);

 

l}catch(Exception e) {

le.printStackTrace();

l}

l}

l}

l【運行結果】:

lhello ,china

lRollen 20

l【案例】調用其餘類的setget方法

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l42

l43

l44

l45

l46

l47

l48

l49

l50

l51

l52

l53

lclasshello {

lpublicstaticvoidmain(String[] args) {

lClass<?> demo =null;

lObject obj=null;

ltry{

ldemo = Class.forName("Reflect.Person");

l}catch(Exception e) {

le.printStackTrace();

l}

ltry{

lobj=demo.newInstance();

l}catch(Exception e) {

le.printStackTrace();

l}

lsetter(obj,"Sex","男",String.class);

lgetter(obj,"Sex");

l}

 

l/**

l* @param obj

l* 操做的對象

l* @param att

l* 操做的屬性

l* */

lpublicstaticvoidgetter(Object obj, String att) {

ltry{

lMethod method = obj.getClass().getMethod("get"+ att);

lSystem.out.println(method.invoke(obj));

l}catch(Exception e) {

le.printStackTrace();

l}

l}

 

l/**

l* @param obj

l* 操做的對象

l* @param att

l* 操做的屬性

l* @param value

l* 設置的值

l* @param type

l* 參數的屬性

l* */

lpublicstaticvoidsetter(Object obj, String att, Object value,

lClass<?> type) {

ltry{

lMethod method = obj.getClass().getMethod("set"+ att, type);

lmethod.invoke(obj, value);

l}catch(Exception e) {

le.printStackTrace();

l}

l}

l}// end class

l【運行結果】:

l

l【案例】經過反射操做屬性

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

lclasshello {

lpublicstaticvoidmain(String[] args)throwsException {

lClass<?> demo =null;

lObject obj =null;

 

ldemo = Class.forName("Reflect.Person");

lobj = demo.newInstance();

 

lField field = demo.getDeclaredField("sex");

lfield.setAccessible(true);

lfield.set(obj,"男");

lSystem.out.println(field.get(obj));

l}

l}// end class

l【案例】經過反射取得並修改數組的信息:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

limportjava.lang.reflect.*;

lclasshello{

lpublicstaticvoidmain(String[] args) {

lint[] temp={1,2,3,4,5};

lClass<?>demo=temp.getClass().getComponentType();

lSystem.out.println("數組類型: "+demo.getName());

lSystem.out.println("數組長度 "+Array.getLength(temp));

lSystem.out.println("數組的第一個元素: "+Array.get(temp,0));

lArray.set(temp,0,100);

lSystem.out.println("修改以後數組第一個元素爲: "+Array.get(temp,0));

l}

l}

l【運行結果】:

l數組類型:int

l數組長度 5

l數組的第一個元素: 1

l修改以後數組第一個元素爲:100

l【案例】經過反射修改數組大小

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

lclasshello{

lpublicstaticvoidmain(String[] args) {

lint[] temp={1,2,3,4,5,6,7,8,9};

lint[] newTemp=(int[])arrayInc(temp,15);

lprint(newTemp);

lSystem.out.println("=====================");

lString[] atr={"a","b","c"};

lString[] str1=(String[])arrayInc(atr,8);

lprint(str1);

l}

 

l/**

l* 修改數組大小

l* */

lpublicstaticObject arrayInc(Object obj,intlen){

lClass<?>arr=obj.getClass().getComponentType();

lObject newArr=Array.newInstance(arr, len);

lintco=Array.getLength(obj);

lSystem.arraycopy(obj,0, newArr,0, co);

lreturnnewArr;

l}

l/**

l* 打印

l* */

lpublicstaticvoidprint(Object obj){

lClass<?>c=obj.getClass();

lif(!c.isArray()){

lreturn;

l}

lSystem.out.println("數組長度爲: "+Array.getLength(obj));

lfor(inti =0; i < Array.getLength(obj); i++) {

lSystem.out.print(Array.get(obj, i)+" ");

l}

l}

l}

l【運行結果】:

l數組長度爲:15

l1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================

l數組長度爲:8

la b c null null null null null

l動態代理

l【案例】首先來看看如何得到類加載器:

l1

l2

l3

l4

l5

l6

l7

l8

l9

lclasstest{

 

l}

lclasshello{

lpublicstaticvoidmain(String[] args) {

ltest t=newtest();

lSystem.out.println("類加載器 "+t.getClass().getClassLoader().getClass().getName());

l}

l}

l【程序輸出】:

l類加載器 sun.misc.Launcher$AppClassLoader

l其實在java中有三種類類加載器。

l1Bootstrap ClassLoader此加載器採用c++編寫,通常開發中不多見。

l2Extension ClassLoader用來進行擴展類的加載,通常對應的是jre\lib\ext目錄中的類

l3AppClassLoader加載classpath指定的類,是最經常使用的加載器。同時也是java中默認的加載器。

l若是想要完成動態代理,首先須要定義一個InvocationHandler接口的子類,已完成代理的具體操做。

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

lpackageReflect;

limportjava.lang.reflect.*;

 

l//定義項目接口

linterfaceSubject {

lpublicString say(String name,intage);

l}

 

l// 定義真實項目

lclassRealSubjectimplementsSubject {

l@Override

lpublicString say(String name,intage) {

lreturnname +" "+ age;

l}

l}

 

lclassMyInvocationHandlerimplementsInvocationHandler {

lprivateObject obj =null;

 

lpublicObject bind(Object obj) {

lthis.obj = obj;

lreturnProxy.newProxyInstance(obj.getClass().getClassLoader(), obj

l.getClass().getInterfaces(),this);

l}

 

l@Override

lpublicObject invoke(Object proxy, Method method, Object[] args)

lthrowsThrowable {

lObject temp = method.invoke(this.obj, args);

lreturntemp;

l}

l}

 

lclasshello {

lpublicstaticvoidmain(String[] args) {

lMyInvocationHandler demo =newMyInvocationHandler();

lSubject sub = (Subject) demo.bind(newRealSubject());

lString info = sub.say("Rollen",20);

lSystem.out.println(info);

l}

l}

l【運行結果】:

lRollen 20

l類的生命週期

l在一個類編譯完成以後,下一步就須要開始使用類,若是要使用一個類,確定離不開JVM。在程序執行中JVM經過裝載,連接,初始化這3個步驟完成。

l類的裝載是經過類加載器完成的,加載器將.class文件的二進制文件裝入JVM的方法區,而且在堆區建立描述這個類的java.lang.Class對象。用來封裝數據。可是同一個類只會被類裝載器裝載之前

l連接就是把二進制數據組裝爲能夠運行的狀態。

 

l連接分爲校驗,準備,解析這3個階段

l校驗通常用來確認此二進制文件是否適合當前的JVM(版本),

l準備就是爲靜態成員分配內存空間,。並設置默認值

l解析指的是轉換常量池中的代碼做爲直接引用的過程,直到全部的符號引用均可以被運行程序使用(創建完整的對應關係)

l完成以後,類型也就完成了初始化,初始化以後類的對象就能夠正常使用了,直到一個對象再也不使用以後,將被垃圾回收。釋放空間。

l當沒有任何引用指向Class對象時就會被卸載,結束類的生命週期

l將反射用於工廠模式

l先來看看,若是不用反射的時候,的工廠模式吧:

lhttp://www.cnblogs.com/rollenholt/archive/2011/08/18/2144851.html

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l/**

l* @author Rollen-Holt 設計模式之 工廠模式

l*/

 

linterfacefruit{

lpublicabstractvoideat();

l}

 

lclassAppleimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Apple");

l}

l}

 

lclassOrangeimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Orange");

l}

l}

 

l// 構造工廠類

l// 也就是說之後若是咱們在添加其餘的實例的時候只須要修改工廠類就好了

lclassFactory{

lpublicstaticfruit getInstance(String fruitName){

lfruit f=null;

lif("Apple".equals(fruitName)){

lf=newApple();

l}

lif("Orange".equals(fruitName)){

lf=newOrange();

l}

lreturnf;

l}

l}

lclasshello{

lpublicstaticvoidmain(String[] a){

lfruit f=Factory.getInstance("Orange");

lf.eat();

l}

 

l}

l這樣,當咱們在添加一個子類的時候,就須要修改工廠類了。若是咱們添加太多的子類的時候,改的就會不少。

l如今咱們看看利用反射機制:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

lpackageReflect;

 

linterfacefruit{

lpublicabstractvoideat();

l}

 

lclassAppleimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Apple");

l}

l}

 

lclassOrangeimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Orange");

l}

l}

 

lclassFactory{

lpublicstaticfruit getInstance(String ClassName){

lfruit f=null;

ltry{

lf=(fruit)Class.forName(ClassName).newInstance();

l}catch(Exception e) {

le.printStackTrace();

l}

lreturnf;

l}

l}

lclasshello{

lpublicstaticvoidmain(String[] a){

lfruit f=Factory.getInstance("Reflect.Apple");

lif(f!=null){

lf.eat();

l}

l}

l}

l如今就算咱們添加任意多個子類的時候,工廠類就不須要修改。

 

l上面的愛嗎雖然能夠經過反射取得接口的實例,可是須要傳入完整的包和類名。並且用戶也沒法知道一個接口有多少個能夠使用的子類,因此咱們經過屬性文件的形式配置所須要的子類。

l下面咱們來看看:結合屬性文件的工廠模式

l首先建立一個fruit.properties的資源文件,

l內容爲:

l1

l2

lapple=Reflect.Apple

lorange=Reflect.Orange

l而後編寫主類代碼:

l1

l2

l3

l4

l5

l6

l7

l8

l9

l10

l11

l12

l13

l14

l15

l16

l17

l18

l19

l20

l21

l22

l23

l24

l25

l26

l27

l28

l29

l30

l31

l32

l33

l34

l35

l36

l37

l38

l39

l40

l41

l42

l43

l44

l45

l46

l47

l48

l49

l50

l51

l52

l53

l54

l55

l56

l57

lpackageReflect;

 

limportjava.io.*;

limportjava.util.*;

 

linterfacefruit{

lpublicabstractvoideat();

l}

 

lclassAppleimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Apple");

l}

l}

 

lclassOrangeimplementsfruit{

lpublicvoideat(){

lSystem.out.println("Orange");

l}

l}

 

l//操做屬性文件類

lclassinit{

lpublicstaticProperties getPro()throwsFileNotFoundException, IOException{

lProperties pro=newProperties();

lFile f=newFile("fruit.properties");

lif(f.exists()){

lpro.load(newFileInputStream(f));

l}else{

lpro.setProperty("apple","Reflect.Apple");

lpro.setProperty("orange","Reflect.Orange");

lpro.store(newFileOutputStream(f),"FRUIT CLASS");

l}

lreturnpro;

l}

l}

 

lclassFactory{

lpublicstaticfruit getInstance(String ClassName){

lfruit f=null;

ltry{

lf=(fruit)Class.forName(ClassName).newInstance();

l}catch(Exception e) {

le.printStackTrace();

l}

lreturnf;

l}

l}

lclasshello{

lpublicstaticvoidmain(String[] a)throwsFileNotFoundException, IOException{

lProperties pro=init.getPro();

lfruit f=Factory.getInstance(pro.getProperty("apple"));

lif(f!=null){

lf.eat();

l}

l}

l}

l【運行結果】:Apple

Java 集合框架

前言:數據結構對程序設計有着深遠的影響,在面向過程的C語言中,數據庫結構用struct來描述,而在面向對象的編程中,數據結構是用類來描述的,而且包含有對該數據結構操做的方法。

Java語言中,Java語言的設計者對經常使用的數據結構和算法作了一些規範(接口)和實現(具體實現接口的類)。全部抽象出來的數據結構和操做(算法)統稱爲Java集合框架(JavaCollectionFramework)。

Java程序員在具體應用時,沒必要考慮數據結構和算法實現細節,只須要用這些類建立出來一些對象,而後直接應用就能夠了,這樣就大大提升了編程效率。

 

1. 先說SetList:

1.1. Set子接口:無序,不容許重複。List子接口:有序,能夠有重複元素。具體區別是

Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引發元素位置改變。<對應類有 HashSet,TreeSet>

List:和數組相似,List能夠動態增加,查找元素效率高,插入刪除元素效率低,由於會引發其餘元素位置改變。<相應類有 ArrayList,LinkedListVector>

SetList具體子類:

2.2.<實例比較>

HashSet:以哈希表的形式存放元素,插入刪除速度很快。

ArrayList:動態數組,LinkedList:鏈表、隊列、堆棧。

Vector是一種老的動態數組,是線程同步的,效率很低,通常不同意使用

1.Collection接口

Collection是最基本的集合接口,一個Collection表明一組Object,即Collection的元素(Elements)。一些 Collection容許相同的元素而另外一些不行。一些能排序而另外一些不行。JavaSDK不提供直接繼承自Collection的類,JavaSDK提供的類都是繼承自Collection子接口ListSet

全部實現Collection接口的類都必須提供兩個標準的構造函數:無參數的構造函數用於建立一個空的Collection,有一個 Collection參數的構造函數用於建立一個新的Collection,這個新的Collection與傳入的Collection有相同的元素。後一個構造函數容許用戶複製一個Collection

如何遍歷Collection中的每個元素?不論Collection的實際類型如何,它都支持一個iterator()的方法,該方法返回一個迭代子,使用該迭代子便可逐一訪問Collection中每個元素。典型的用法以下:

Iteratorit=collection.iterator();//得到一個迭代子

while(it.hasNext()){

Objectobj=it.next();//獲得下一個元素

}

Collection接口派生的兩個接口是ListSet

2.List接口

List是有序的Collection,使用此接口可以精確的控制每一個元素插入的位置。用戶可以使用索引(元素在List中的位置,相似於數組下標)來訪問List中的元素,這相似於Java的數組。

和下面要提到的Set不一樣,List容許有相同的元素。

除了具備Collection接口必備的iterator()方法外,List還提供一個listIterator()方法,返回一個 ListIterator接口,和標準的Iterator接口相比,ListIterator多了一些add()之類的方法,容許添加,刪除,設定元素,還能向前或向後遍歷。

實現List接口的經常使用類有LinkedListArrayListVectorStack

2.1.LinkedList

LinkedList實現了List接口,容許null元素。此外LinkedList提供額外的getremoveinsert方法在 LinkedList的首部或尾部。這些操做使LinkedList可被用做堆棧(stack),隊列(queue)或雙向隊列(deque)。

注意LinkedList沒有同步方法。若是多個線程同時訪問一個List,則必須本身實現訪問同步。一種解決方法是在建立List時構造一個同步的List

Listlist=Collections.synchronizedList(newLinkedList(...));

2.2.ArrayList

ArrayList實現了可變大小的數組。它容許全部元素,包括nullArrayList沒有同步。

sizeisEmptygetset方法運行時間爲常數。可是add方法開銷爲分攤的常數,添加n個元素須要O(n)的時間。其餘的方法運行時間爲線性。

每一個ArrayList實例都有一個容量(Capacity),即用於存儲元素的數組的大小。這個容量可隨着不斷添加新元素而自動增長,可是增加算法並無定義。當須要插入大量元素時,在插入前能夠調用ensureCapacity方法來增長ArrayList的容量以提升插入效率(自動增判斷長度後增加也會浪費時間的呀!)。

LinkedList同樣,ArrayList也是非同步的(unsynchronized)。(擴展閱讀:在java.util.concurrent包中定義的CopyOnWriteArrayList提供了線程安全的Arraylist,可是當進行addset等變化操做時它是經過爲底層數組建立新的副本實現的,因此比較耗費資源

(源碼在此:publicbooleanadd(E e) {

finalReentrantLocklock =this.lock;

lock.lock();

try{

Object[] elements = getArray();

intlen = elements.length;

Object[] newElements = Arrays.copyOf(elements,len + 1);

newElements[len] = e;

setArray(newElements);

returntrue;

}finally{

lock.unlock();

}

}

可是若是存在頻繁遍歷,遍歷操做比變化(寫入和修改)操做多的時候這種遍歷就相對於本身進行的同步遍歷效果要好,並且它也容許存在null元素)

2.3.Vector

Vector很是相似ArrayList,可是Vector是同步的。由Vector建立的Iterator,雖然和ArrayList建立的 Iterator是同一接口,可是,由於Vector是同步的,當一個Iterator被建立並且正在被使用,另外一個線程改變了Vector的狀態(例如,添加或刪除了一些元素),這時調用Iterator的方法時將拋出ConcurrentModificationException,所以必須捕獲該異常。經過使用capacityensurecapacity操做以及capacityIncrement域能夠優化存儲操做,這個前面講過,(VectorIteratorlistIterator方法翻譯的迭代器支持fail-fast機制,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。官方對此的說明是java.util包中的集合類都返回fail-fast迭代器,這意味着它們假設線程在集合內容中進行迭代時,集合不會更改它的內容。若是fail-fast迭代器檢測到在迭代過程當中進行了更改操做,那麼它會拋出ConcurrentModificationException,這是不可控異常。

2.4.Stack

Stack繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被看成堆棧使用。基本的pushpop方法,還有peek方法獲得棧頂的元素,empty方法測試堆棧是否爲空,search方法檢測一個元素在堆棧中的位置。Stack剛建立後是空棧。

stack有幾個比較實用的方法

boolean

empty()
測試堆棧是否爲空。

E

peek()
查看堆棧頂部的對象,但不從堆棧中移除它。

E

pop()
移除堆棧頂部的對象,並做爲此函數的值返回該對象。

E

push(Eitem)
把項壓入堆棧頂部。

int

search(Objecto)
返回對象在堆棧中的位置,以 1 爲基數。

 

 

 

3.set接口:

Set具備與Collection徹底同樣的接口,所以沒有任何額外的功能,不像前面有兩個不一樣的List。實際上Set就是Collection,只是行爲不一樣。(這是繼承與多態思想的典型應用:表現不一樣的行爲。)Set不保存重複的元素(至於如何判斷元素相同則較爲負責)
Set : 存入Set的每一個元素都必須是惟一的,由於Set不保存重複元素。加入Set的元素必須定義equals()方法以確保對象的惟一性。SetCollection有徹底同樣的接口。Set接口不保證維護元素的次序。(我變換黃色背景那裏的名稱獲得以下特色)


HashSet : 它不容許出現重複元素;不保證和政集合中元素的順序,能夠本身作個例子能夠看出加入的字段順序跟遍歷出的不同,容許包含值爲null的元素,但最多隻能有一個null元素(不容許重複嘛!)。

 

TreeSet : 能夠實現排序等功能的集合,它在講對象元素添加到集合中時會自動按照某種比較規則將其插入到有序的對象序列中,並保證該集合元素組成按照升序排列。

a)(在對大量信息進行檢索的時候,TreeSetAraayList更有效率,能保證在log(n)的時間內完成)。

b)TreeSet是實用樹形結構來存儲信息的,每一個節點都會保存一下指針對象,分別指向父節點,左分支,右分支,相比較而言,ArrayList就是一個含有元素的簡單數組了,正由於如此,它佔的內存也要比ArrayList多一些。

c)TreeSet插入元素也比ArrayList要快一些,由於當元素插入到ArrayList的任意位置時,平均每次要移動一半的列表,須要O(n)的時間, 而TreeSet深度遍歷查詢花費的實施只須要O(log(n))(廣泛的都是,set查詢慢,插入快,list查詢快,插入滿, .TODO:這一點我會寫一個算法測試文章具體分析一下


LinkedHashSet : 具備HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。因而在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。

 

 

PS:set有幾個比較好的方法:

removeAll(Collection<?> c)

移除 set 中那些包含在指定 collection 中的元素(可選操做)。

boolean retainAll(Collection<?> c)

僅保留 set 中那些包含在指定 collection 中的元素(可選操做)。

containsAll(Collection<?> c)

若是此 set 包含指定 collection 的全部元素,則返回 true

4.Queue數據結構

這方面知識涉及到線程比較多,有線程基礎的口語參考這篇文章

http://blog.csdn.net/a512592151/article/details/38454745

5.Map的功能方法

java爲數據結構中的映射定義了一個接口java.util.Map;它有四個實現類,分別是HashMap Hashtable LinkedHashMapTreeMap

Map主要用於存儲健值對,根據鍵獲得值,所以不容許鍵重複,但容許值重複。

Hashmap 是一個 最經常使用的Map,它根據鍵的HashCode 值存儲數據,根據鍵能夠直接獲取它的值,具備很快的訪問速度。HashMap最多隻容許一條記錄的鍵爲Null;容許多條記錄的值爲 Null;HashMap不支持線程的同步,即任一時刻能夠有多個線程同時寫HashMap;可能會致使數據的不一致。若是須要同步,能夠用 CollectionssynchronizedMap方法使HashMap具備同步的能力.

Hashtable HashMap相似,不一樣的是:它不容許記錄的鍵或者值爲空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,所以也致使了Hashtale在寫入時會比較慢。

LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的.在遍歷 的時候會比HashMap慢。

TreeMap可以把它保存的記錄根據鍵排序,默認是按升序排序,也能夠指定排序的比較器,當用Iterator 遍歷TreeMap時,獲得的記錄是排過序的。

 

package com.hxw.T2;

 

import java.util.HashMap;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.LinkedHashMap;

import java.util.Map;

import java.util.TreeMap;

 

 

 

public class MapTester {

 

public static void init(Map map) {

if (map != null) {

String key = null;

for (int i = 5; i > 0; i--) {

key = new Integer(i).toString() + ".0";

map.put(key, key.toString());

// Map中的鍵是不重複的,若是插入兩個鍵值同樣的記錄,

// 那麼後插入的記錄會覆蓋先插入的記錄

map.put(key, key.toString() + "0");

}

}

}

 

public static void output(Map map) {

if (map != null) {

Object key = null;

Object value = null;

// 使用迭代器遍歷Map的鍵,根據鍵取值

Iterator it = map.keySet().iterator();

while (it.hasNext()) {

key = it.next();

value = map.get(key);

System.out.println("key: " + key + "; value: " + value);

}

// 或者使用迭代器遍歷Map的記錄Map.Entry

Map.Entry entry = null;

it = map.entrySet().iterator();

while (it.hasNext()) {

// 一個Map.Entry表明一條記錄

entry = (Map.Entry) it.next();

// 經過entry能夠得到記錄的鍵和值

// System.out.println("key: " + entry.getKey() + "; value: " +

// entry.getValue());

}

}

}

 

public static boolean containsKey(Map map, Object key) {

if (map != null) {

return map.containsKey(key);

}

return false;

}

 

public static boolean containsValue(Map map, Object value) {

if (map != null) {

return map.containsValue(value);

}

return false;

}

 

public static void testHashMap() {

Map myMap = new HashMap();

init(myMap);

// HashMap的鍵能夠爲null

myMap.put(null, "ddd");

// HashMap的值能夠爲null

myMap.put("aaa", null);

output(myMap);

}

 

public static void testHashtable() {

Map myMap = new Hashtable();

init(myMap);

// Hashtable的鍵不能爲null

// myMap.put(null,"ddd");

// Hashtable的值不能爲null

// myMap.put("aaa", null);

output(myMap);

}

 

public static void testLinkedHashMap() {

Map myMap = new LinkedHashMap();

init(myMap);

// LinkedHashMap的鍵能夠爲null

myMap.put(null, "ddd");

// LinkedHashMap的值能夠爲null

myMap.put("aaa", null);

output(myMap);

}

 

public static void testTreeMap() {

Map myMap = new TreeMap();

init(myMap);

// TreeMap的鍵不能爲null

// myMap.put(null,"ddd");

// TreeMap的值不能爲null

// myMap.put("aaa", null);

output(myMap);

}

 

public static void main(String[] args) {

System.out.println("採用HashMap");

MapTester.testHashMap();

System.out.println("採用Hashtable");

MapTester.testHashtable();

System.out.println("採用LinkedHashMap");

MapTester.testLinkedHashMap();

System.out.println("採用TreeMap");

MapTester.testTreeMap();

 

Map myMap = new HashMap();

MapTester.init(myMap);

System.out.println("新初始化一個Map: myMap");

MapTester.output(myMap);

// 清空Map

myMap.clear();

System.out.println("將myMap clear後,myMap空了麼? " + myMap.isEmpty());

MapTester.output(myMap);

myMap.put("aaa", "aaaa");

myMap.put("bbb", "bbbb");

// 判斷Map是否包含某鍵或者某值

System.out.println("myMap包含鍵aaa? "

+ MapTester.containsKey(myMap, "aaa"));

System.out.println("myMap包含值aaaa? "

+ MapTester.containsValue(myMap, "aaaa"));

// 根據鍵刪除Map中的記錄

myMap.remove("aaa");

System.out.println("刪除鍵aaa後,myMap包含鍵aaa? "

+ MapTester.containsKey(myMap, "aaa"));

// 獲取Map的記錄數

System.out.println("myMap包含的記錄數: " + myMap.size());

}

}


附:map 遍歷的四種方法:

public static void main(String[] args) {

 

 

Map<String, String> map = new HashMap<String, String>();

map.put("1", "value1");

map.put("2", "value2");

map.put("3", "value3");

 

//第一種:廣泛使用,二次取值

System.out.println("經過Map.keySet遍歷key和value:");

for (String key : map.keySet()) {

System.out.println("key= "+ key + " and value= " + map.get(key));

}

 

//第二種

System.out.println("經過Map.entrySet使用iterator遍歷key和value:");

Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();

while (it.hasNext()) {

Map.Entry<String, String> entry = it.next();

System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());

}

 

//第三種:推薦,尤爲是容量大時

System.out.println("經過Map.entrySet遍歷key和value");

for (Map.Entry<String, String> entry : map.entrySet()) {

System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());

}

 

//第四種

System.out.println("經過Map.values()遍歷全部的value,但不能遍歷key");

for (String v : map.values()) {

System.out.println("value= " + v);

}

}

 

 

3、其餘特徵

*ListSetMap將持有對象一概視爲Object型別。

*CollectionListSetMap都是接口,不能實例化。

繼承自它們的 ArrayList, Vector, HashTable, HashMap是具象class,這些纔可被實例化。

*vector容器確切知道它所持有的對象隸屬什麼型別。vector不進行邊界檢查。

 

3、Collections

Collections是針對集合類的一個幫助類。提供了一系列靜態方法實現對各類集合的搜索、排序、線程徹底化等操做。

至關於對Array進行相似操做的類——Arrays

如,Collections.max(Collection coll); coll中最大的元素。

Collections.sort(List list); list中元素排序

4、如何選擇?

1、容器類和Array的區別、擇取

* 容器類僅能持有對象引用(指向對象的指針),而不是將對象信息copy一份至數列某位置。

* 一旦將對象置入容器內,便損失了該對象的型別信息。

2

* 在各類Lists中,最好的作法是以ArrayList做爲缺省選擇。當插入、刪除頻繁時,使用LinkedList()

Vector老是比ArrayList慢,因此要儘可能避免使用。

* 在各類Sets中,HashSet一般優於TreeSet(插入、查找)。只有當須要產生一個通過排序的序列,才用TreeSet

TreeSet存在的惟一理由:可以維護其內元素的排序狀態。

* 在各類Maps

HashMap用於快速查找。

* 當元素個數固定,用Array,由於Array效率是最高的。

結論:最經常使用的是ArrayListHashSetHashMapArray。並且,咱們也會發現一個規律,TreeXXX都是排序的。

 

注意:

1Collection沒有get()方法來取得某個元素。只能經過iterator()遍歷元素。

2SetCollection擁有如出一轍的接口。

3List,能夠經過get()方法來一次取出一個元素。使用數字來選擇一堆對象中的一個,get(0)...(add/get)

4、通常使用ArrayListLinkedList構造堆棧stack、隊列queue

5Mapput(k,v) / get(k),還能夠使用containsKey()/containsValue()來檢查其中是否含有某個key/value

HashMap會利用對象的hashCode來快速找到key

* hashing

哈希碼就是將對象的信息通過一些轉變造成一個獨一無二的int值,這個值存儲在一個array中。

咱們都知道全部存儲結構中,array查找速度是最快的。因此,能夠加速查找。

 

發生碰撞時,讓array指向多個values。即,數組每一個位置上又生成一個槤表。

6Map中元素,能夠將key序列、value序列單獨抽取出來。

使用keySet()抽取key序列,map中的全部keys生成一個Set

使用values()抽取value序列,map中的全部values生成一個Collection

爲何一個生成Set,一個生成Collection?那是由於,key老是獨一無二的,value容許重複。

淺談JAVA集合框架

Java提供了數種持有對象的方式,包括語言內置的Array,還有就是utilities中提供的容器類(container classes),又稱羣集類(collection classes)。集合在java中很是重要,在討論以前,先來看幾個面試中的經典問題。
1 Collection Collections的區別。
2 List, Set, Map是否繼承自Collection接口。
3 ArrayListVector的區別。
4 HashMapHashtable的區別。

篇尾有答案,咱們開始正題。

集合Collection接口

--Collection 是任何對象組,元素各自獨立,一般擁有相同的套用規則。Set List由它派生。

基本操做增長元素add(Object obj); addAll(Collection c);

刪除元素 remove(Object obj);removeAll(Collection c);

求交集 retainAll(Collection c);

刪除元素 remove(Object obj);removeAll(Collection c);

求交集 retainAll(Collection c);

訪問/遍歷集合元素的好辦法是使用Iterator接口(迭代器用於取代Enumeration)

PublicinterfaceIterator{

PublicBooleanhasNext(};

PublicObjectnext(};

Publicvoidremove(};

}

set
--沒有重複項目的集合

有三種特定類型的集可用

HashSet-基於散列表的集,加進散列表的元素要實現hashCode()方法

LinkedHashSet-對集迭代時,按增長順序返回元素

TreeSet-基於(平衡)樹的數據結構

清單List

--位置性集合。加進清單的元素能夠加在清單中特定位置或加到末尾

有兩個特定版本

ArrayList(數組表)-相似於Vector,都用於縮放數組維護集合。區別:

.同步性:Vector是線程安全的,也就是說是同步的,而ArrayList是線程序不安全的,不是同步的
.數據增加:當須要增加時,Vector默認增加爲原來一培,而ArrayList倒是原來的一半

LinkedList(鏈表)-是雙向鏈表,每一個節點都有兩個指針指向上一節點和下一節點。

用在FIFO,用addList()加入元素 removeFirst()刪除元素
用在FILO,addFirst()/removeLast()

ListIterator提供雙向遍歷next() previous(),可刪除、替換、增長元素

映射表Map

--用於關鍵字/數值對,像個Dictionary

處理Map的三種集合

關鍵字集KeySet()

數值集value()

項目集enrySet()

四個具體版本

HashMap-散列表的通用映射表

LinkedHashMap-擴展HashMap,對返回集合迭代時,維護插入順序

WeakHashMap-基於弱引用散列表的映射表,若是不保持映射表外的關鍵字的引用,則內存回收程序會回收它

TreeMap-基於平衡樹的映射表

HashMap-散列表的通用映射表

LinkedHashMap-擴展HashMap,對返回集合迭代時,維護插入順序

WeakHashMap-基於弱引用散列表的映射表,若是不保持映射表外的關鍵字的引用,則內存回收程序會回收它

TreeMap-基於平衡樹的映射表
Collections類,用於同步集合,還能改變集合只讀方式的類
e.g.:

Mapmp=newHashMap();

mp=Collections.synchronizedMap(mp);//生成線程安全的映射表

mp=Collections.unmodifiableMap(mp);//生成只讀映射表

Comparable 天然順序的排序類Comparator 面向樹的集合排序類

容器分類學(Container taxonomy)

集合接口: Collection List Set;Map Iterator ListIterator

抽象類: AbstractCollection AbstractList AbstractSet AbstractMap AbstractSequentiaList

老版本中的集合類型

Vector

Vector,就是向量。一種異構的混合體,能夠動態增長容量。對它的操做簡要以下

好比咱們有一個Vector: Vector myVec=new Vector(a_Array.length)

取得vector的長度:myVec.size();

賦值:set(int position,Object obj) / setElementAt(Object obj, int position) –不支持動態增加

add(Object obj )/ addElement(Object obj) Vector末尾加入對象

e.g.myVec.add(new a_Array[0]);

取出元素:get(int position) / getElement(int position)

Stack

Vector的子類。就是數據結構裏講濫了的堆棧(這個詞可簡稱棧,不要混淆於heap-堆)。後進先出的存取方式。

Stack()構造空棧

Empty()叛空

Search()檢查堆棧是否有元素

Peek()取得棧頂元素

Pop()彈棧

Push()入棧

Enumeration接口

Dictionary

字典。關鍵字/數值方式存取數據,若是映射沒有此關鍵字,取回null

Hashtable

Dictionary結構的具體實現。

面試題答案

Collection Collections的區別。

Collections是個java.util下的類,它包含有各類有關集合操做的靜態方法。

Collection是個java.util下的接口,它是各類集合結構的父接口

List, Set, Map是否繼承自Collection接口?ListSetMap不是

ArrayListVector的區別。
.同步性:Vector是線程安全的,也就是說是同步的,而ArrayList是線程序不安全的,不是同步的
.數據增加:當須要增加時,Vector默認增加爲原來一培,而ArrayList倒是原來的一半

HashMapHashtable的區別
.歷史緣由:Hashtable是基於陳舊的Dictionary類的,HashMapJava1.2引進的Map接口的一個實現

.同步性:Hashtable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的

.值:只有HashMap可讓你將空值做爲一個表的條目的keyvalue

 

Java 多線程 併發編程

1、多線程

1、操做系統有兩個容易混淆的概念,進程和線程。

進程:一個計算機程序的運行實例,包含了須要執行的指令;有本身的獨立地址空間,包含程序內容和數據;不一樣進程的地址空間是互相隔離的;進程擁有各類資源和狀態信息,包括打開的文件、子進程和信號處理。

線程:表示程序的執行流程,是CPU調度執行的基本單位;線程有本身的程序計數器、寄存器、堆棧和幀。同一進程中的線程共用相同的地址空間,同時共享進進程鎖擁有的內存和其餘資源。

2Java標準庫提供了進程和線程相關的API,進程主要包括表示進程的java.lang.Process類和建立進程的java.lang.ProcessBuilder類;

表示線程的是java.lang.Thread類,在虛擬機啓動以後,一般只有Java類的main方法這個普通線程運行,運行時能夠建立和啓動新的線程;還有一類守護線程(damon thread),守護線程在後臺運行,提供程序運行時所需的服務。當虛擬機中運行的全部線程都是守護線程時,虛擬機終止運行。

3、線程間的可見性:一個線程對進程中共享的數據的修改,是否對另外一個線程可見

可見性問題:

aCPU採用時間片輪轉等不一樣算法來對線程進行調度

[java]view plaincopy

1.publicclassIdGenerator{

2.privateintvalue=0;

3.publicintgetNext(){

4.returnvalue++;

5.}

6.}

對於IdGeneratorgetNext()方法,在多線程下不能保證返回值是不重複的:各個線程之間相互競爭CPU時間來獲取運行機會,CPU切換可能發生在執行間隙。

以上代碼getNext()的指令序列:CPU切換可能發生在7條指令之間,多個getNext的指令交織在一塊兒。

[java]view plaincopy

1.aload_0

2.dup

3.getfield#12

4.dup_x1

5.iconst_1

6.iadd

7.putfield#12

bCPU緩存:

目前CPU通常採用層次結構的多級緩存的架構,有的CPU提供了L1L2L3三級緩存。當CPU須要讀取主存中某個位置的數據時,會一次檢查各級緩存中是否存在對應的數據。若是有,直接從緩存中讀取,這比從主存中讀取速度快不少。當CPU須要寫入時,數據先被寫入緩存中,以後再某個時間點寫回主存。因此某些時間點上,緩存中的數據與主存中的數據多是不一致。

c、指令順序重排

出行性能考慮,編譯器在編譯時可能會對字節代碼的指令順序進行從新排列,以優化指令的執行順序,在單線程中不會有問題,但在多線程可能產生與可見性相關的問題。

2、Java內存模型(Java Memory Model

屏蔽了CPU緩存等細節,只關注主存中的共享變量;關注對象的實例域、靜態域和數組元素;關注線程間的動做。

1volatile關鍵詞:用來對共享變量的訪問進行同步,上一次寫入操做的結果對下一次讀取操做是確定可見的。(在寫入volatile變量值以後,CPU緩存中的內容會被寫回內存;在讀取volatile變量時,CPU緩存中的對應內容會被置爲失效,從新從主存中進行讀取),volatile不使用鎖,性能優於synchronized關鍵詞。

用來確保對一個變量的修改被正確地傳播到其餘線程中。

例子:A線程是Worker,一直跑循環,B線程調用setDone(true)A線程即中止任務

[java]view plaincopy

1.publicclassWorker{

2.privatevolatilebooleandone;

3.publicvoidsetDone(booleandone){

4.this.done=done;

5.}

6.publicvoidwork(){

7.while(!done){

8.//執行任務;

9.}

10.}

11.}

例子:錯誤使用。由於沒有鎖的支持,volatile的修改不能依賴於當前值,當前值可能在其餘線程中被修改。(Worker是直接賦新值與當前值無關)

[java]view plaincopy

1.publicclassCounter{

2.publicvolatilestaticintcount=0;

3.publicstaticvoidinc(){

4.//這裏延遲1毫秒,使得結果明顯

5.try{

6.Thread.sleep(1);

7.}catch(InterruptedExceptione){

8.}

9.count++;

10.}

11.publicstaticvoidmain(String[]args){

12.//同時啓動1000個線程,去進行i++計算,看看實際結果

13.for(inti=0;i<1000;i++){

14.newThread(newRunnable(){

15.@Override

16.publicvoidrun(){

17.Counter.inc();

18.}

19.}).start();

20.}

21.//這裏每次運行的值都有可能不一樣,可能不爲1000

22.System.out.println("運行結果:Counter.count="+Counter.count);

23.}

24.}

2final關鍵詞
final關鍵詞聲明的域的值只能被初始化一次,通常在構造方法中初始化。。(在多線程開發中,final域一般用來實現不可變對象)

當對象中的共享變量的值不可能發生變化時,在多線程中也就不須要同步機制來進行處理,故在多線程開發中應儘量使用不可變對象

另外,在代碼執行時,final域的值能夠被保存在寄存器中,而不用從主存中頻繁從新讀取。

3java基本類型的原子操做

1)基本類型,引用類型的複製引用是原子操做;(即一條指令完成)

2longdouble的賦值,引用是能夠分割的,非原子操做;

3)要在線程間共享longdouble的字段時,必須在synchronized中操做,或是聲明成volatile

3、Java提供的線程同步方式

1synchronized關鍵字

方法或代碼塊的互斥性來完成實際上的一個原子操做。(方法或代碼塊在被一個線程調用時,其餘線程處於等待狀態)

全部的Java對象都有一個與synchronzied關聯的監視器對象(monitor),容許線程在該監視器對象上進行加鎖和解鎖操做。

a、靜態方法:Java類對應的Class類的對象所關聯的監視器對象。

b、實例方法:當前對象實例所關聯的監視器對象。

c、代碼塊:代碼塊聲明中的對象所關聯的監視器對象。

注:當鎖被釋放,對共享變量的修改會寫入主存;當活得鎖,CPU緩存中的內容被置爲無效。編譯器在處理synchronized方法或代碼塊,不會把其中包含的代碼移動到synchronized方法或代碼塊以外,從而避免了因爲代碼重排而形成的問題。

例:如下方法getNext()getNextV2() 都得到了當前實例所關聯的監視器對象

[java]view plaincopy

1.publicclassSynchronizedIdGenerator{

2.privateintvalue=0;

3.publicsynchronizedintgetNext(){

4.returnvalue++;

5.}

6.publicintgetNextV2(){

7.synchronized(this){

8.returnvalue++;

9.}

10.}

11.}

2Object類的waitnotifynotifyAll方法

生產者和消費者模式,判斷緩衝區是否滿來消費,緩衝區是否空來生產的邏輯。若是用while volatile也能夠作,不過本質上會讓線程處於忙等待,佔用CPU時間,對性能形成影響。

wait: 將當前線程放入,該對象的等待池中,線程A調用了B對象的wait()方法,線程A進入B對象的等待池,而且釋放B的鎖。(這裏,線程A必須持有B的鎖,因此調用的代碼必須在synchronized修飾下,不然直接拋出java.lang.IllegalMonitorStateException異常)。

notify:將該對象中等待池中的線程,隨機選取一個放入對象的鎖池,噹噹前線程結束後釋放掉鎖, 鎖池中的線程便可競爭對象的鎖來得到執行機會。

notifyAll:將對象中等待池中的線程,所有放入鎖池。

notify鎖喚醒的線程選擇由虛擬機實現來決定,不能保證一個對象鎖關聯的等待集合中的線程按照所指望的順序被喚醒,極可能一個線程被喚醒以後,發現他所要求的條件並無知足,而從新進入等待池。由於當等待池中包含多個線程時,通常使用notifyAll方法,不過該方法會致使線程在沒有必要的狀況下被喚醒,以後又立刻進入等待池,對性能有影響,不過能保證程序的正確性)

工做流程:

aConsumer線程A 來 看產品,發現產品爲空,調用產品對象的wait(),線程A進入產品對象的等待池並釋放產品的鎖。

bProducer線程B得到產品的鎖,執行產品的notifyAll()Consumer線程A從產品的等待池進入鎖池,Producer線程B生產產品,而後退出釋放鎖。

cConsumer線程A得到產品鎖,進入執行,發現有產品,消費產品,而後退出。

例子:

[java]view plaincopy

1.publicsynchronizedStringpop(){

2.this.notifyAll();//喚醒對象等待池中的全部線程,可能喚醒的就是生產者(當生產者發現產品滿,就會進入對象的等待池,這裏代碼省略,基本略同)

3.while(index==-1){//若是發現沒產品,就釋放鎖,進入對象等待池

4.this.wait();

5.}//當生產者生產完後,消費者從this.wait()方法再開始執行,第一次還會執行循環,萬一產品仍是爲空,則再等待,因此這裏必須用while循環,不能用if

6.Stringgood=buffer[index];

7.buffer[index]=null;

8.index--;

9.returngood;//消費完產品,退出。

10.}

注:wait()方法有超時和不超時之分,超時的在通過一段時間,線程還在對象的等待池中,那麼線程也會推出等待狀態。

3、線程狀態轉換:

已經廢棄的方法:stopsuspendresumedestroy,這些方法在實現上時不安全的。

線程的狀態:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITING(有超時的等待)、TERMINATED

a、方法sleep()進入的阻塞狀態,不會釋放對象的鎖(即你們一塊兒睡,誰也別想執行代碼),因此不要讓sleep方法處在synchronized方法或代碼塊中,不然形成其餘等待獲取鎖的線程長時間處於等待。

b、方法join()則是主線程等待子線程完成,再往下執行。例如main方法新建兩個線程AB

[java]view plaincopy

1.publicstaticvoidmain(String[]args)throwsInterruptedException{

2.Threadt1=newThread(newThreadTesterA());

3.Threadt2=newThread(newThreadTesterB());

4.t1.start();

5.t1.join();//等t1執行完再往下執行

6.t2.start();

7.t2.join();//在虛擬機執行中,這句可能被忽略

8.}

c、方法interrupt(),向被調用的對象線程發起中斷請求。如線程A經過調用線程Bdinterrupt方法來發出中斷請求,線程B來處理這個請求,固然也能夠忽略,這不是必須的。Object類的wait()Thread類的join()sleep方法都會拋出受檢異常java.lang.InterruptedException,經過interrupt方法中斷該線程會致使線程離開等待狀態。對於wait()調用來講,線程須要從新獲取監視器對象上的鎖以後才能拋出InterruptedException異常,並致以異常的處理邏輯。

能夠經過Thread類的isInterrupted方法來判斷是否有中斷請求發生,一般能夠利用這個方法來判斷是否退出線程(相似上面的volatitle修飾符的例子);

Thread類還有個方法Interrupted(),該方法不但能夠判斷當前線程是否被中斷,還會清楚線程內部的中斷標記,若是返回true,即曾被請求中斷,同時調用完後,清除中斷標記。

若是一個線程在某個對象的等待池,那麼notifyinterrupt 均可以使該線程從等待池中被移除。若是同時發生,那麼看實際發生順序。若是是notify先,那照常喚醒,沒影響。若是是interrupt先,而且虛擬機選擇讓該線程中斷,那麼即便nofity,也會忽略該線程,而喚醒等待池中的另外一個線程。

eyield(),嘗試讓出所佔有的CPU資源,讓其餘線程獲取運行機會,對操做系統上的調度器來講是一個信號,不必定當即切換線程。(在實際開發中,測試階段頻繁調用yeid方法使線程切換更頻繁,從而讓一些多線程相關的錯誤更容易暴露出來)。

 

4、非阻塞方式

線程之間同步機制的核心是監視對象上的鎖,競爭鎖來得到執行代碼的機會。當一個對象獲取對象的鎖,而後其餘嘗試獲取鎖的對象會處於等待狀態,這種鎖機制的實現方式很大程度限制了多線程程序的吞吐量和性能(線程阻塞),且會帶來死鎖(線程Aa對象鎖,等着獲取b對象鎖,線程Bb對象鎖,等待獲取a對象鎖)和優先級倒置(優先級低的線程得到鎖,優先級高的只能等待對方釋放鎖)等問題。

若是能不阻塞線程,又能保證多線程程序的正確性,就能有更好的性能。

在程序中,對共享變量的使用通常遵循必定的模式,即讀取、修改和寫入三步組成。以前碰到的問題是,這三步執行中可能線程執行切換,形成非原子操做。鎖機制是把這三步變成一個原子操做。

目前CPU自己實現 將這三步 合起來 造成一個原子操做,無需線程鎖機制干預,常見的指令是比較和替換compare and swap,CAS),這個指令會先比較某個內存地址的當前值是否是指定的舊指,若是是,就用新值替換,不然什麼也不作,指令返回的結果是內存地址的當前值。經過CAS指令能夠實現不依賴鎖機制的非阻塞算法。通常作法是把CAS指令的調用放在一個無限循環中,不斷嘗試,知道CAS指令成功完成修改。

java.util.concurrent.atomic包中提供了CAS指令。(不是全部CPU都支持CAS,在某些平臺,java.util.concurrent.atomic的實現仍然是鎖機制)

atomic包中提供的Java類分紅三類:

1、支持以原子操做來進行更新的數據類型的JavaAtomicBooleanAtomicIntegerAtomicReference),在內存模型相關的語義上,這四個類的對象相似於volatile變量。

類中的經常使用方法:

acompareAndSet:接受兩個參數,一個是指望的舊值,一個是替換的新值。

bweakCompareAndSet:效果同compareAndSetJSR中表示weak原子方式讀取和有條件地寫入變量但不建立任何 happen-before 排序,但在源代碼中和compareAndSet徹底同樣,因此並無按JSR實現)

cgetset:分別用來直接獲取和設置變量的值。

dlazySet:與set相似,但容許編譯器把lazySet方法的調用與後面的指令進行重排,所以對值得設置操做有可能被推遲。

例:

[java]view plaincopy

1.publicclassAtomicIdGenerator{

2.privatefinalAtomicIntercounter=newAtomicInteger(0);

3.publicintgetNext(){

4.returncounter.getAndIncrement();

5.}

6.}

7.//getAndIncrement方法的內部實現方式,這也是CAS方法的通常模式,CAS方法不必定成功,因此包裝在一個無限循環中,直到成功

8.publicfinalintgetAndIncrement(){

9.for(;;){

10.intcurrent=get();

11.intnext=current+1;

12.if(compareAndSet(current,next))

13.returncurrent;

14.}

15.}

2、提供對數組類型的變量進行處理的Java類,AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray類。(同上,只是放在類數組裏,調用時也只是多了一個操做元素索引的參數)

3、經過反射的方式對任何對象中包含的volatitle變量使用CAS方法AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater。他們提供了一種方式把CAS的功能擴展到了任何Java類中聲明爲volatitle的域上。(靈活,但語義較弱,由於對象的volatitle可能被非atomic的其餘方式被修改)

[java]view plaincopy

1.publicclassTreeNode{

2.privatevolatileTreeNodeparent;

3.//靜態工廠方法

4.privatestaticfinalAtomicReferenceFieldUpdater<TreeNode,TreeNode>parentUpdater=AtomicReferenceFieldUpdater.newUpdater(TreeNode.class,TreeNode.class,"parent");

5.publicbooleancompareAndSetParent(TreeNodeexpect,TreeNodeupdate){

6.returnparentUpdater.compareAndSet(this,expect,update);

7.}

8.}

注:java.util.concurrent.atomic包中的Java類屬於比較底層的實現,通常做爲java.util.concurrent包中不少非阻塞的數據結構的實現基礎。

比較多的用AtomicBooleanAtomicIntegerAtomicLongAtomicReference。在實現線程安全的計數器時,AtomicIntegerAtomicLong類時最佳的選擇。

5、高級同步機制(比synchronized更靈活的加鎖機制)

synchronizedvolatile,以及waitnotify等方法抽象層次低,在程序開發中使用比較繁瑣,易出錯。

而多線程之間的交互來講,存在某些固定的模式,如生產者-消費者和讀者-寫者模式,把這些模式抽象成高層API,使用起來會很是方便。

java.util.concurrent包爲多線程提供了高層的API,知足平常開發中的常見需求。

經常使用接口

1Lock接口,表示一個鎖方法:

alock(),獲取所,若是沒法獲取所鎖,會處於等待狀態

bunlock(),釋放鎖。(通常放在finally代碼塊中)

clockInterruptibly(),與lock()相似,但容許當前線程在等待獲取鎖的過程當中被中斷。(因此要處理InterruptedException

dtryLock(),以非阻塞方式獲取鎖,若是沒法獲取鎖,則返回false。(tryLock()的另外一個重載能夠指定超時,若是指定超時,當沒法獲取鎖,會等待而阻塞,同時線程能夠被中斷)

2ReadWriteLock接口,表示兩個鎖,讀取的共享鎖和寫入的排他鎖。(適合常見的讀者--寫者場景)

ReadWriteLock接口的readLockwriteLock方法來獲取對應的鎖的Lock接口的實現。

在多數線程讀取,少數線程寫入的狀況下,能夠提升多線程的性能,提升使用該數據結構的吞吐量。

若是是相反的狀況,較多的線程寫入,則接口會下降性能。

3ReentrantLock類和ReentrantReadWriteLock,分別爲上面兩個接口的實現類。

他們具備重入性:即容許一個線程屢次獲取同一個鎖(他們會記住上次獲取鎖而且未釋放的線程對象,和加鎖的次數,getHoldCount()

同一個線程每次獲取鎖,加鎖數+1,每次釋放鎖,加鎖數-1,到0,則該鎖被釋放,能夠被其餘線程獲取。

[java]view plaincopy

1.publicclassLockIdGenrator{

2.//newReentrantLock(true)是重載,使用更加公平的加鎖機制,在鎖被釋放後,會優先給等待時間最長的線程,避免一些線程長期沒法得到鎖

3.privateintReentrantLocklock=ReentrantLock();

4.privafteintvalue=0;

5.publicintgetNext(){

6.lock.lock();//進來就加鎖,沒有鎖會等待

7.try{

8.returnvalue++;//實際操做

9.}finally{

10.lock.unlock();//釋放鎖

11.}

12.}

13.}

注:重入性減小了鎖在各個線程之間的等待,例如便利一個HashMap,每次next()以前加鎖,以後釋放,能夠保證一個線程一口氣完成便利,而不會每次next()以後釋放鎖,而後和其餘線程競爭,下降了加鎖的代價, 提供了程序總體的吞吐量。(即,讓一個線程一口氣完成任務,再把鎖傳遞給其餘線程)。
4Condition接口,Lock接口代替了synchronizedCondition接口替代了objectwaitnofity

aawait(),使當前線程進入等待狀態,知道被喚醒或中斷。重載形式能夠指定超時時間。

bawaitNanos(),以納秒爲單位等待。

cawaitUntil(),指定超時發生的時間點,而不是通過的時間,參數爲java.util.Date

dawaitUninterruptibly(),前面幾種會響應其餘線程發出的中斷請求,他會無視,直到被喚醒。

注:與Object類的wait()相同,await()會釋放其所持有的鎖。

esignal()signalAll, 至關於 notifynotifyAll

[java]view plaincopy

1.Locklock=newReentrantLock();

2.Conditioncondition=lock.newCondition();

3.lock.lock();

4.try{

5.while(/*邏輯條件不知足*/){

6.condition.await();

7.}

8.}finally{

9.lock.unlock();

10.}

6、底層同步器

多線程程序中,線程之間存在多種不一樣的同步方式。除了Java標準庫提供的同步方式以外,程序中特有的同步方式須要由開發人員本身來實現。

常見的一種需求是 對有限個共享資源的訪問,好比多臺我的電腦,2臺打印機,當多個線程在等待同一個資源時,從公平角度出發,會用FIFO隊列。

若是程序中的同步方式能夠抽象成對有限個資源的訪問,那麼能夠使用java.util.concurrent.locks包中的AbstractQueuedSynchronizer類和AbstractQueuedLongSynchronizer類做爲實現的基礎,前者用int類型的變量來維護內部狀態,然後者用long類型。(能夠將這個變量理解爲共享資源個數)

經過getStatesetState、和compareAndSetState3個方法更新內部變量的值。

AbstractQueuedSynchronizer類是abstract的,須要覆蓋其中包含的部分方法,一般作法是把其做爲一個Java類的內部類,外部類提供具體的同步方式,內部類則做爲實現的基礎。有兩種模式,排他模式和共享模式,分別對應方法 tryAcquire()tryRelease tryAcquireSharedtryReleaseShared,在這些方法中,使用getStatesetStatecompareAndSetState3個方法來修改內部變量的值,以此來反應資源的狀態。

[java]view plaincopy

1.publicclassSimpleResourceManager{

2.privatefinalInnerSynchronizersynchronizer;

3.privatestaticclassInnerSynchronizerextendsAbstractQueuedSynchronizer{

4.InnerSynchronizer(intnumOfResources){

5.setState(numOfResources);

6.}

7.protectedinttryAcquireShared(intacquires){

8.for(;;){

9.intavailable=getState();

10.intremain=available-acquires;

11.if(remain<0||comapreAndSetState(available,remain){

12.returnremain;

13.}

14.}

15.}

16.protectedbooleantryReleaseShared(intreleases){

17.for(;;){

18.intavailable=getState();

19.intnext=available+releases;

20.if(compareAndSetState(available,next){

21.returntrue;

22.}

23.}

24.}

25.}

26.publicSimpleResourceManager(intnumOfResources){

27.synchronizer=newInnerSynchronizer(numOfResources);

28.}

29.publicvoidacquire()throwsInterruptedException{

30.synchronizer.acquireSharedInterruptibly(1);

31.}

32.pubicvoidrelease(){

33.synchronizer.releaseShared(1);

34.}

35.}

7、高級同步對象(提升開發效率)

atomiclocks包提供的Java類能夠知足基本的互斥和同步訪問的需求,但這些Java類的抽象層次較低,使用比較複雜。

更簡單的作法是使用java.util.concurrent包中的高級同步對象。

1、信號量。

信號量通常用來數量有限的資源,每類資源有一個對象的信號量,信號量的值表示資源的可用數量。

在使用資源時,須要從該信號量上獲取許可,成功獲取許可,資源的可用數-1;完成對資源的使用,釋放許可,資源可用數+1; 當資源數爲0時,須要獲取資源的線程以阻塞的方式來等待資源,或過段時間以後再來檢查資源是否可用。(上面的SimpleResourceManager類實際上時信號量的一個簡單實現)

java.util.concurrent.Semaphore類,在建立Semaphore類的對象時指定資源的可用數

aacquire(),以阻塞方式獲取許可

btryAcquire(),以非阻塞方式獲取許可

crelease(),釋放許可。

daccquireUninterruptibly()accquire()方法獲取許能夠的過程能夠被中斷,若是不但願被中斷,使用此方法。

[java]view plaincopy

1.publicclassPrinterManager{

2.privatefinalSemphoresemaphore;

3.privatefinalList<Printer>printers=newArrayList<>():

4.publicPrinterManager(Collection<?extendsPrinter>printers){

5.this.printers.addAll(printers);

6.//這裏重載方法,第二個參數爲true,以公平競爭模式,防止線程飢餓

7.this.semaphore=newSemaphore(this.printers.size(),true);

8.}

9.publicPrinteracquirePrinter()throwsInterruptedException{

10.semaphore.acquire();

11.returngetAvailablePrinter();

12.}

13.publicvoidreleasePrinter(Printerprinter){

14.putBackPrinter(pinter);

15.semaphore.release();

16.}

17.privatesynchronizedPrintergetAvailablePrinter(){

18.printerresult=printers.get(0);

19.printers.remove(0);

20.returnresult;

21.}

22.privatesynchronizedvoidputBackPrinter(Printerprinter){

23.printers.add(printer);

24.}

25.}

2、倒數閘門

多線程協做時,一個線程等待另外的線程完成任務才能繼續進行。

java.util.concurrent.CountDownLatch類,建立該類時,指定等待完成的任務數;當一個任務完成,調用countDonw(),任務數-1。等待任務完成的線程經過await(),進入阻塞狀態,直到任務數量爲0CountDownLatch類爲一次性,一旦任務數爲0,再調用await()再也不阻塞當前線程,直接返回。

例:

[java]view plaincopy

1.publicclassPageSizeSorter{

2.//併發性能遠遠優於HashTable的Map實現,hashTable作任何操做都須要得到鎖,同一時間只有有個線程能使用,而ConcurrentHashMap是分段加鎖,不一樣線程訪問不一樣的數據段,徹底不受影響,忘記HashTable吧。

3.privatestaticfinalConcurrentHashMap<String,Interger>sizeMap=newConcurrentHashMap<>();

4.privatestaticclassGetSizeWorkerimplementsRunnable{

5.privatefinalStringurlString;

6.publicGetSizeWorker(StringurlString,CountDownLatchsignal){

7.this.urlString=urlStirng;

8.this.signal=signal;

9.}

10.publicvoidrun(){

11.try{

12.InputStreamis=newURL(urlString).openStream();

13.intsize=IOUtils.toByteArray(is).length;

14.sizeMap.put(urlString,size);

15.}catch(IOExceptione){

16.sizeMap.put(urlString,-1);

17.}finally{

18.signal.countDown()://完成一個任務,任務數-1

19.}

20.}

21.}

22.privatevoidsort(){

23.List<Entry<String,Integer>list=newArrayList<sizeMap.entrySet());

24.Collections.slort(list,newComparator<Entry<String,Integer>>(){

25.publicintcompare(Entry<String,Integer>o1,Entry<Sting,Integer>o2){

26.returnInteger.compare(o2.getValue(),o1.getValue());

27.};

28.System.out.println(Arrays.deepToString(list.toArray()));

29.}

30.publicvoidsortPageSize(Collection<String>urls)throwsInterruptedException{

31.CountDownLatchsortSignal=newCountDownLatch(urls.size());

32.for(Stringurl:urls){

33.newThread(newGetSizeWorker(url,sortSignal)).start();

34.}

35.sortSignal.await()://主線程在這裏等待,任務數歸0,則繼續執行

36.sort();

37.}

38.}

3、循環屏障

循環屏障在做用上相似倒數閘門,不過他不像倒數閘門是一次性的,能夠循環使用。另外,線程之間是互相平等的,彼此都須要等待對方完成,當一個線程完成本身的任務以後,等待其餘線程完成。當全部線程都完成任務以後,全部線程才能夠繼續運行。

當線程之間須要再次進行互相等待時,能夠複用同一個循環屏障。

java.uti.concurrent.CyclicBarrier用來表示循環屏障,建立時指定使用該對象的線程數目,還能夠指定一個Runnable接口的對象做爲每次循環後執行的動做。(當最後一個線程完成任務以後,全部線程繼續執行以前,被執行。若是線程之間須要更新一些共享的內部狀態,能夠利用這個Runnalbe接口的對象來處理)。

每一個線程任務完成以後,經過調用await方法進行等待,當全部線程都調用await方法以後,處於等待狀態的線程均可以繼續執行。在全部線程中,只要有一個在等待中被中斷,超時或是其餘錯誤,整個循環屏障會失敗,全部等待中的其餘線程拋出java.uti.concurrent.BrokenBarrierException

例:每一個線程負責找一個數字區間的質數,當全部線程完成後,若是質數數目不夠,繼續擴大範圍查找

[java]view plaincopy

1.publicclassPrimeNumber{

2.privatestaticfinalintTOTAL_COUTN=5000;

3.privatestaticfinalintRANGE_LENGTH=200;

4.privatestaticfinalintWORKER_NUMBER=5;

5.privatestaticvolatitlebooleandone=false;

6.privatestaticintrangeCount=0;

7.privatestaticfinalList<Long>results=newArrayList<Long>():

8.privatestaticfinalCyclicBarrierbarrier=newCyclicBarrier(WORKER_NUMBER,newRunnable(){

9.publicvoidrun(){

10.if(results.size()>=TOTAL_COUNT){

11.done=true;

12.}

13.}

14.});

15.privatestaticclassPrimeFinderimplementsRunnable{

16.publicvoidrun(){

17.while(!done){//整個過程在一個while循環下,await()等待,下次循環開始,會再次判斷執行條件

18.intrange=getNextRange();

19.longstart=rang*RANGE_LENGTH;

20.longend=(range+1)*RANGE_LENGTH;

21.for(longi=start;i<end;i++){

22.if(isPrime(i)){

23.updateResult(i);

24.}

25.}

26.try{

27.barrier.await();

28.}catch(InterruptedException|BokenBarrierExceptione){

29.done=true;

30.}

31.}

32.}

33.}

34.privatesynchronizedstaticvoidupdateResult(longvalue){

35.results.add(value);

36.}

37.privatesynchronizedstaticintgetNextRange(){

38.returnrangeCount++;

39.}

40.privatestaticbooleanisPrime(longnumber){

41.//找質數的代碼

42.}

43.publicvoidcalculate(){

44.for(inti=0;i<WORKER_NUMBER;i++){

45.newThread(newPrimeFinder()).start();

46.}

47.while(!done){

48.

49.}

50.//計算完成

51.}

52.}

4、對象交換器
適合於兩個線程須要進行數據交換的場景。(一個線程完成後,把結果交給另外一個線程繼續處理)

java.util.concurrent.Exchanger類,提供了這種對象交換能力,兩個線程共享一個Exchanger類的對象,一個線程完成對數據的處理以後,調用Exchanger類的exchange()方法把處理以後的數據做爲參數發送給另一個線程。而exchange方法的返回結果是另一個線程鎖提供的相同類型的對象。若是另一個線程未完成對數據的處理,那麼exchange()會使當前線程進入等待狀態,直到另一個線程也調用了exchange方法來進行數據交換。

例:

[java]view plaincopy

1.publicclassSendAndReceiver{

2.privatefinalExchanger<StringBuilder>exchanger=newExchanger<StringBuilder>();

3.privateclassSenderimplementsRunnable{

4.publicvoidrun(){

5.try{

6.StringBuildercontent=newStringBuilder("Hello");

7.content=exchanger.exchange(content);

8.}catch(InterruptedExceptione){

9.Thread.currentThread().interrupt();

10.}

11.}

12.}

13.privateclassReceiverimplementsRunnable{

14.publicvoidrun(){

15.try{

16.StringBuildercontent=newStringBuilder("World");

17.content=exchanger.exchange(content);

18.}catch(InterruptedExceptione){

19.Thread.currentThread().interrupt();

20.}

21.}

22.}

23.publicvoidexchange(){

24.newThread(newSender()).start();

25.newThread(newReceiver()).start();

26.}

27.}

8、數據結構(多線程程序使用的高性能數據結構)

java.util.concurrent包中提供了一些適合多線程程序使用的高性能數據結構,包括隊列和集合類對象等。

1、隊列

aBlockingQueue接口:線程安全的阻塞式隊列;當隊列已滿時,想隊列添加會阻塞;當隊列空時,取數據會阻塞。(很是適合消費者-生產者模式)

阻塞方式:put()take()

非阻塞方式:offer()poll()

實現類:基於數組的固定元素個數的ArrayBolockingQueue和基於鏈表結構的不固定元素個數的LinkedBlockQueue類。

bBlockingDeque接口: 與BlockingQueue類似,但能夠對頭尾進行添加和刪除操做的雙向隊列;方法分爲兩類,分別在隊首和對尾進行操做。

實現類:標準庫值提供了一個基於鏈表的實現,LinkedBlockgingDeque

2、集合類

在多線程程序中,若是共享變量時集合類的對象,則不適合直接使用java.util包中的集合類。這些類要麼不是線程安全,要麼在多線程下性能比較差。

應該使用java.util.concurrent包中的集合類。

aConcurrentMap接口: 繼承自java.util.Map接口

putIfAbsent():只有在散列表不包含給定鍵時,纔會把給定的值放入。

remove():刪除條目。

replace(key,value):把value 替換到給定的key上。

replace(key, oldvalue, newvalue)CAS的實現。

實現類:ConcurrentHashMap

建立時,若是能夠預估可能包含的條目個數,能夠優化性能。(由於動態調整所能包含的數目操做比較耗時,這個HashMap也同樣,只是多線程下更耗時)。

建立時,預估進行更新操做的線程數,這樣實現中會根據這個數把內部空間劃分爲對應數量的部分。(默認是16,若是隻有一個線程進行寫操做,其餘都是讀取,那麼把值設爲1 能夠提升性能)。

注:當從集合中建立出迭代器遍歷Map元素時,不必定能看到正在添加的數據,只能和集合保證弱一致性。(固然使用迭代器不會由於查看正在改變的Map,而拋出java.util.ConcurrentModifycationException

bCopyOnWriteArrayList接口:繼承自java.util.List接口。

顧名思義,在CopyOnWriteArrayList的實現類,全部對列表的更新操做都會新建立一個底層數組的副本,並使用副原本存儲數據;對列表更新操做加鎖,讀取操做不加鎖。

適合多讀取少修改的場景,若是更新操做多,那麼不適合用,一樣迭代器只能表示建立時列表的狀態,更新後使用了新的底層數組,迭代器仍是引用舊的底層數組。

9、多線程任務的執行

過去線程的執行,是先建立Thread類的想,再調用start方法啓動,這種作法要求開發人員對線程進行維護,在線程較多時,通常建立一個線程池同一管理,同時下降重複建立線程的開銷

J2SE5.0中,java.util.concurrent包提供了豐富的用來管理線程和執行任務的實現。

1、基本接口(描述任務)

aCallable接口:

Runnable接口受限於run方法的類型簽名,而Callable只有一個方法call(),能夠有返回值,能夠拋出受檢異常。

bFuture接口:

過去,須要異步線程的任務執行結果,要求主線程和任務執行線程之間進行同步和數據傳遞。

Future簡化了任務的異步執行,做爲異步操做的一個抽象。調用get()方法能夠獲取異步的執行結果,若是任務沒有執行完,會等待,直到任務完成或被取消,cancel()能夠取消。

cDelayed接口:

延遲執行任務,getDelay()返回當前剩餘的延遲時間,若是不大於0,說明延遲時間已通過去,應該調度並執行該任務。

2、組合接口(描述任務)

aRunnableFuture接口:繼承自Runnable接口和Future接口。

當來自Runnalbe接口中的run方法成功執行以後,至關於Future接口表示的異步任務已經完成,能夠經過get()獲取運行結果。

bScheduledFuture接口:繼承Future接口和Delayed接口,表示一個能夠調用的異步操做。

cRunnableScheduledFuture接口:繼承自RunnableDelayedFuture,接口中包含isPeriodic,代表該異步操做是否能夠被重複執行。

3Executor接口、ExcutorServer接口、ScheduleExecutorService接口和CompletionService接口(描述任務執行)

aexecutor接口,execute()用來執行一個Runnable接口的實現對象,不一樣的Executor實現採起不一樣執行策略,但提供的任務執行功能比較弱。

bexcutorServer接口,繼承自executor

提供了對任務的管理:submit(),能夠吧CallableRunnable做爲任務提交,獲得一個Future做爲返回,能夠獲取任務結果或取消任務。

提供批量執行:invokeAll()invokeAny(),同時提交多個CallableinvokeAll(),會等待全部任務都執行完成,返回一個包含每一個任務對應Future的列表;invokeAny(),任何一個任務成功完成,即返回該任務結果。

提供任務關閉:shutdown()shutdownNow()來關閉服務,前者不容許新的任務提交,後者試圖終止正在運行和等待的任務,並返回已經提交單沒有被運行的任務列表。(兩個方法都不會等待服務真正關閉,只是發出關閉請求。)。shutdownDow,一般作法是向線程發出中斷請求,因此確保提交的任務實現了正確的中斷處理邏輯。

cScheduleExecutorService接口,繼承自excutorServer接口:支持任務的延遲執行和按期執行,能夠執行CallableRunnable

schedule(),調度一個任務在延遲若干時間以後執行;

scheduleAtFixedRate():在初始延遲後,每隔一段時間循環執行;在下一次執行開始時,上一次執行可能還未結束。(同一時間,可能有多個)

scheduleWithFixedDelay:同上,只是在上一次任務執行完後,通過給定的間隔時間再開始下一次執行。(同一時間,只有一個)

以上三個方法都返回ScheduledFuture接口的實現對象。

dCompletionService接口,共享任務執行結果。

一般在使用ExecutorService接口,經過submit提交任務,並獲得一個Future接口來獲取任務結果,若是任務提交者和執行結果的使用者是程序的不一樣部分,那就要把Future在不一樣部分進行傳遞;而CompletionService就是解決這個問題,程序不一樣部分能夠共享CompletionService,任務提交後,執行結果能夠經過take(阻塞),poll(非阻塞)來獲取。

標準庫提供的實現是 ExecutorCompletionService,在建立時,須要提供一個Executor接口的實現做爲參數,用來實際執行任務。

例:多線程方式下載文件

[java]view plaincopy

1.publicclassFileDownloader{

2.//線程池

3.privatefinalExecutorServiceexecutor=Executors.newFixedThreadPool(10);

4.publicbooleandownload(finalURLurl,finalPathpath){

5.Future<Path>future=executor.submit(newCallable<Path>(){//submit提交任務

6.publicPathcall(){

7.//這裏就省略IOException的處理了

8.InputStreamis=url.openStream();

9.Files.copy(is,path,StandardCopyOption.REPLACE_EXISTING);

10.returnpath;

11.});

12.try{

13.returnfuture.get()!=null?true:false;

14.}<spanstyle="font-family:Arial,Helvetica,sans-serif;">catch(InterruptedException|ExecutionExceptione){</span>

15.returnfalse;

16.}

17.}

18.publicvoidclose(){//當再也不使用FileDownloader類的對象時,應該使用close方法關閉其中包含的ExecutorService接口的實現對象,不然虛擬機不會退出,佔用內存不釋放

19.executor.shutdown();//發出關閉請求,此時不會再接受新任務

20.try{

21.if(!executor.awaitTermination(3,TimeUnit.MINUTES)){//awaitTermination來等待一段時間,使正在執行的任務或等待的任務有機會完成

22.executor.shutdownNow();//若是等待時間事後還有任務沒完成,則強制結束

23.executor.awaitTermination(1,TimeUnit.MINUTES);//再等待一段時間,使被強制結束的任務完成必要的清理工做

24.}

25.}catch(InterruptedExceptione){

26.executor.shutdownNow();

27.Thread.currentThread().interrupt();

28.}

29.}

30.}

10、Java SE7 新特性

java.util.concurrent包進行更新,增長了新的輕量級任務執行框架fork/join和多階段線程同步工具。

1、輕量級任務執行框架fork/join

這個框架的目的主要是更好地利用底層平臺上的多核和多處理器來進行並行處理。

經過分治算法或map/reduce算法來解決問題。

fork/join 類比於 map/reduce

fork操做是把一個大的問題劃分爲若干個較小的問題,劃分過程通常爲遞歸,直到能夠直接進行計算的粒度適合的子問題;子問題在結算後,能夠獲得整個問題的部分解

join操做收集子結果,合併,獲得完整解,也多是 遞歸進行的。

相對通常的線程池實現,F/J框架的優點在任務的處理方式上。在通常線程池中,一個線程因爲某些緣由沒法運行,會等待;而在F/J,某個子問題因爲等待另一個子問題的完成而沒法繼續運行,那麼處理該子問題的線程會主動尋找其餘還沒有運行的子問題來執行。這種方式減小了等待時間,提升了性能。

爲了F/J能高效,在每一個子問題視線中應避免使用synchronized或其餘方式進行同步,也不該使用阻塞式IO或過多訪問共享變量。在理想狀況下,每一個子問題都應值進行CPU計算,只使用每一個問題的內部對象,惟一的同步應只發生在子問題和建立它的父問題之間。(這徹底就是HadoopMapReduce嘛)

aForkJoinTask:表示一個由F/J框架執行的任務,該類實現了Future接口,能夠按照Future接口的方式來使用。(表示任務)

fork(),異步方式啓動任務的執行。

join(),等待任務完成並返回執行結果。

在建立本身的任務時,最好不要直接繼承自ForkJoinTask,而是繼承其子類,RecuriveTaskRecursiveAction,前者能夠返回結果,後者不行。

bForkJoinPool:表示任務執行,實現了ExecutorService接口,除了能夠執行ForkJoinTask,也能夠執行CallableRunnable。(任務執行)

執行任務的兩大類:

第一類:executeinvokesubmit方法:直接提交任務。

第二類:fork():運行ForkJoinTask在執行過程當中的子任務。

通常做法是表示整個問題的ForkJoinTask用第一類提交,執行過程當中產生的子任務不須要處理,ForkJoinPool會負責子任務執行。

例:查找數組中的最大值

[java]view plaincopy

1.privatestaticclassMaxValueTaskextendsRecursiveTask<Long>{

2.privatefinallong[]array;

3.privatefinalintstart;

4.privatefinalintend;

5.MaxValueTask(long[]array,intstart,intend){

6.this.array=array;

7.this.start=start;

8.this.end=end;

9.}

10.//compute是RecursiveTask的主方法

11.protectedlongcompute(){

12.longmax=Long.MIN_VALUE;

13.if(end-start<RANG_LENGTH){//尋找最大值

14.for(inti=start;i<end;i++{

15.if(array[i]>max){

16.max=array[i];

17.}

18.}

19.}else{//二分任務

20.intmid=(start+end)/2;

21.MaxValueTasklowTask=newMaxValueTask(array,start,mid);

22.MaxValueTaskhighTask=newMaxValueTask(array,mid,end);

23.lowTask.fork();//異步啓動任務

24.highTask.fork();

25.max=Math.max(max,lowTask.join());//等待執行結果

26.max=Math.max(max,highTask.join();

27.}

28.returnmax;

29.}

30.publicLongcalculate(long[]array){

31.MaxValueTasktask=newMaxValueTask(array,0,array.length);

32.Longresult=forkJoinPool.invoke(task);

33.returnresult;

34.}

35.}

注:這個例子是示例,但從性能上說直接對整個數組順序比較效率高,畢竟多線程所帶來的額外開銷過大。

在實際中,F/J框架發揮做用的場合不少,好比在一個目錄包含的全部文本中搜索某個關鍵字,能夠每一個文件建立一個子任務。

若是相關的功能能夠用遞歸和分治來解決,就適合F/J

2、多階段線程同步工具

Phaser類是Java SE 7中新增的一個使用同步工具,功能和靈活性比倒數閘門和循環屏障要強不少。

F/J框架中的子任務之間要進行同步時,應優先考慮Phaser

Phaser把多個線程寫做執行的任務劃分紅多個階段(phase),編程時要明確各個階段的任務,每一個階段均可以有任意個參與者,線程能夠隨時註冊並參與到某個階段,當一個階段中全部線程都成功完成以後,PhaseronAdvance()被調用,能夠經過覆蓋添加自定義處理邏輯(相似循環屏障的使用的Runnable接口),而後Phaser類會自動進入下個階段。如此循環,知道Phaser再也不包含任何參與者。

Phaser建立後,初始階段編號爲0,構造函數中指定初始參與個數。

register()bulkRegister(),動態添加一個或多個參與者。

arrive(),某個參與者完成任務後調用

arriveAndDeregister(),任務完成,取消本身的註冊。

arriveAndAwaitAdvance(),本身完成等待其餘參與者完成。,進入阻塞,直到Phaser成功進入下個階段。

awaitAdvance()awaitAdvanceInterruptibly(),等待phaser進入下個階段,參數爲當前階段的編號,後者能夠設置超時和處理中斷請求。

另外,Phaser的一個重要特徵是多個Phaser能夠組成樹形結構,Phaser提供了構造方法來指定當前對象的父對象;當一個子對象參與者>0,會自動註冊到父對象中;當=0,自動解除註冊。

例:從指定網址,下載img標籤的照片

階段1、處理網址對應的html文本,和抽取img的連接;2、建立圖片下載子線程,主線程等待;3、子線程下載圖片,主線程等待;4、任務完成退出

[java]view plaincopy

1.publicclassWebPageImageDownloader{

2.privatefinalPhaserphaser=newPhaser(1);//初始參與數1,表明主線程。

3.publicvoiddownload(URLurl,finalPathpath)throwsIOException{

4.Stringcontent=getContent(url);//得到HTML文本,省略。

5.List<URL>imageUrls=extractImageUrls(content);//得到圖片連接,省略。

6.for(finalURLimageUrl:imageUrls){

7.phaser.register();//子線程註冊

8.newThread(){

9.publicvoidrun(){

10.phaser.arriveAndAwaitAdvance();//第二階段的等待,等待進入第三階段

11.try{

12.InputStreamis=imageUrl.openStream();

13.File.copy(is,getSavePath(path,imageUrl),StandardCopyOption.REPLACE_EXISTING);

14.}catch(IOExceptione){

15.e.printStackTrace():

16.}finally{

17.phaser.arriveAndDeregister();//子線程完成任務,退出。

18.}

19.}

20.}.start();

21.}

22.phaser.arriveAndAwaitAdvance();//第二階段等待,子線程在註冊

23.phaser.arriveAndAwaitAdvance();//第三階段等待,子線程在下載

24.phaser.arriveAndDeregister();//全部線程退出。

25.}

26.}

11、ThreadLocal

java.lang.ThreadLocal,線程局部變量,把一個共享變量變爲一個線程的私有對象。不一樣線程訪問一個ThreadLocal類的對象時,鎖訪問和修改的事每一個線程變量各自獨立的對象。經過ThreadLocal能夠快速把一個非線程安全的對象轉換成線程安全的對象。(同時也就不能達到數據傳遞的做用了)。

aget()set()分別用來獲取和設置當前線程中包含的對象的值。

bremove(),刪除。

cinitialValue(),初始化值。若是沒有經過set方法設置值,第一個調用get,會經過initValue來獲取對象的初始值。

ThreadLoacl的通常用法,建立一個ThreadLocal的匿名子類並覆蓋initalValue(),把ThreadLoacl的使用封裝在另外一個類中

[java]view plaincopy

1.publicclassThreadLocalIdGenerator{

2.privatestaticfinalThreadLocal<IdGenerator>idGenerator=newThreadLocal<IdGenerator>(){

3.protectedIdGeneratorinitalValue(){

4.returnnewIdGenerator();//IdGenerator是個初始intvalue=0,而後getNext(){returnvalue++}

5.}

6.};

7.publicstaticintgetNext(){

8.returnidGenerator.get().getNext();

9.}

10.}

ThreadLoal的另一個做用是建立線程惟一的對象,在有些狀況,一個對象在代碼中各個部分都須要用到,傳統作法是把這個對象做爲參數在代碼間傳遞,若是使用這個對I昂的代碼都在同一個線程,能夠封裝在ThreadLocal中。

如:在多線程中,生成隨機數

java.util.Random會帶來競爭問題,java.util.concurrent.ThreadLocalRandom類提供多線程下的隨機數聲場,底層是ThreadLoacl

總結:多線程開發中應該優先使用高層API,若是沒法知足,使用java.util.concurrent.atomicjava.util.concurrent.locks包提供的中層API,而synchronizedvolatile,以及wait,notifynotifyAll等低層API 應該最後考慮。

java多線程基本入門(代碼)
java多線程編程仍是比較重要的,在實際業務開發中常常要遇到這個問題。 java多線程,傳統建立線程的方式有兩種。 一、繼承自Thread類,覆寫run方法。 二、實現Runnable接口,實現run方法。 啓動線程的方法都是調用start方法,真正執行調用的是run方法。
參考代碼以下:

複製代碼代碼以下:


package com.jack.thread;

/**
* 線程簡單演示例子程序
*
* @author pinefantasy
* @since 2013-10-31
*/
public class ThreadDemo1 {

/**
* 第一種方式:繼承自Thread類,覆寫run方法
*/
public static class Test1Thread extends Thread {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test1," + Thread.currentThread().getName() + ", i = " + i);
}
}
}

/**
* 第二種方式:實現Runnable接口,實現run方法
*/
public static class Test2Thread implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test2," + Thread.currentThread().getName() + ", i = " + i);
}
}

}

/**
* <pre>
*
* 主線程爲main線程
* 分支線程爲:1 2 3 三種簡單實現方式
*
* @param args
*/
public static void main(String[] args) {
new Test1Thread().start();// 啓動線程1
new Thread(new Test2Thread()).start();// 啓動線程2
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test3," + Thread.currentThread().getName() + ", i = " + i);
}
}
}).start();// 啓動線程3
}

}

2、java併發包簡單入門
多個線程,統一處理同一個變量演示代碼:

複製代碼代碼以下:


package com.jack.thread;

import java.util.concurrent.atomic.AtomicInteger;

/**
* 多線程對同一個變量進行操做
*
* @author pinefantasy
* @since 2013-10-31
*/
public class ThreadDemo2 {

private static int count = 0;

public static class CountThread implements Runnable {// 1.這邊有線程安全問題,共享變量亂套了

@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName() + ", count = " + count);
}
}

}

private static final Object lock = new Object();// 這邊使用的lock對象

public static class Count2Thread implements Runnable {// 這邊使用的是互斥鎖方式

@Override
public void run() {
synchronized (lock) {// 使用互斥鎖方式處理
for (int i = 0; i < 100; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ", count = " + count);
}
}
}

}

private static AtomicInteger ai = new AtomicInteger();// 這邊使用的是併發包的AtomicXXX類,使用的是CAS方式:compare and swap

public static class Count3Thread implements Runnable {// AtomicInteger內部的CAS實現方式,採用的是:循環、判斷、設置三部曲方式

@Override
public void run() {
for (int i = 0; i < 100; i++) {
int tmp = ai.incrementAndGet();// 採用CAS方式處理
System.out.println(Thread.currentThread().getName() + ", count = " + tmp);
}
}

}

private static volatile int countV = 0;// 定義成volatile,讓多線程感知,由於值是放在主存中

public static class Count4Thread implements Runnable {// volatile定義的變量只是說放到了主存,當時++操做並非原子操做,這個要當心

@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(50);// 這邊讓線程休眠下,增長出錯機率
} catch (InterruptedException e) {
e.printStackTrace();
}
countV++;// volatile要正確使用,不是說定義成volatile就是安全的,仍是要注意++ --操做並非原子操做
System.out.println(Thread.currentThread().getName() + ", count = " + countV);
}
}

}

/**
* 使用泛型簡單編寫一個測試方法
*
* @param <T>
* @param t
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InterruptedException
*/
public static <T> void testTemplate(T t) throws InstantiationException, IllegalAccessException, InterruptedException {
for (int i = 0; i < 5; i++) {
if (t instanceof Runnable) {
Class<?> c = t.getClass();
Object object = c.newInstance();
new Thread((Runnable) object).start();
}
}
}

/**
* <pre>
* 1.test1 線程不安全演示例子,count變量不能獲得預期的效果
* 2.test2 在test1基礎上改進的,用互斥鎖sync處理
* 3.test3 在test1基礎上改進的,用AtomicInteger類來實現
* 4.test4 有問題的方法,由於i++並非原子操做,將count定義爲volatile類型的
*
* @param args
* @throws InterruptedException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws InterruptedException, InstantiationException, IllegalAccessException {
// 1.測試1
// testTemplate(new CountThread());
// 2.測試2
// testTemplate(new Count2Thread());
// 3.測試3
// testTemplate(new Count3Thread());
// 4.測試4
testTemplate(new Count4Thread());
Thread.sleep(15000);
System.out.println(count);
System.out.println(ai.get());
System.out.println(countV);
}

}

生產者-消費者模式
生產者(生成產品的線程)--》負責生成產品 消費者(消費產品的線程)--》負責消費產品
買車人、消費者。 賣車人、銷售汽車的人、姑且當作生產者。 倉庫、存放汽車的地方。 汽車工廠、真實生成汽車的地方。
參考代碼以下:
// 沒有加上同步機制的代碼以下:

複製代碼代碼以下:


package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo3.CarBigHouse.Car;

/**
* 第一個版本的生產者和消費者線程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo3 {

/**
* 姑且賣車的當作是生產者線程
*/
public static class CarSeller implements Runnable {

private CarBigHouse bigHouse;

public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作生產者線程,往倉庫裏邊增長汽車,實際上是觸發增長汽車
int count = bigHouse.put();
System.out.println("生產汽車-->count = " + count);
}
}

}

/**
* 姑且買車的人當作是消費者線程
*/
public static class Consumer implements Runnable {

private CarBigHouse bigHouse;

public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作消費者線程,從倉庫裏邊提取汽車,實際上是觸發,從倉庫裏邊提取一輛汽車出來
int count = bigHouse.get();
System.out.println("消費汽車-->count = " + count);
}
}

}

/**
* 這邊姑且當作是車子big house放車子的倉庫房
*/
public static class CarBigHouse {

public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list

public int put() {// 提供給生產者放汽車到倉庫的接口
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到倉庫中去
carNums++;// 總數增長1
return carNums;
}

public int get() {// 提供給消費者從這邊取汽車接口
Car car = null;
if (carList.size() != 0) {// size不爲空纔去取車
car = carList.get(carList.size() - 1);// 提取最後一個car
carList.remove(car);// 從從庫list中移除掉
carNums--;// 總數減小1
}
return carNums;
}

public static class Car {

public String carName;// 汽車名稱
public double carPrice;// 汽車價格

public Car() {
}

public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}

/**
* 採用靜態工廠方式建立car對象,這個只是簡單模擬,不作設計模式上的過多考究
*/
public static class CarFactory {

private CarFactory() {
}

public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}

public static Car makeNewCar() {
return new Car();
}
}

/**
* 第一個版本的生產者和消費者線程,沒有加上同步機制的演示例子
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}

}

// 加上互斥鎖的代碼以下:

複製代碼代碼以下:


package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;

/**
* 第二個版本的生產者消費者線程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo4 {

/**
* 姑且賣車的當作是生產者線程
*/
public static class CarSeller implements Runnable {

private CarBigHouse bigHouse;

public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作生產者線程,往倉庫裏邊增長汽車,實際上是觸發增長汽車
int count = bigHouse.put();
System.out.println("生產汽車-->count = " + count);
}
}

}

/**
* 姑且買車的人當作是消費者線程
*/
public static class Consumer implements Runnable {

private CarBigHouse bigHouse;

public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作消費者線程,從倉庫裏邊提取汽車,實際上是觸發,從倉庫裏邊提取一輛汽車出來
int count = bigHouse.get();
System.out.println("消費汽車-->count = " + count);
}
}

}

/**
* 這邊姑且當作是車子big house放車子的倉庫房
*/
public static class CarBigHouse {

public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list

// 直接增長上synchronized關鍵字方式,成員方法,鎖的是當前bigHouse對象
// 這種鎖是互斥鎖,方法在同一個時刻,只有一個線程能夠訪問到裏邊的代碼

public synchronized int put() {// 提供給生產者放汽車到倉庫的接口
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到倉庫中去
carNums++;// 總數增長1
return carNums;
}

public synchronized int get() {// 提供給消費者從這邊取汽車接口
Car car = null;
if (carList.size() != 0) {// size不爲空纔去取車
car = carList.get(carList.size() - 1);// 提取最後一個car
carList.remove(car);// 從從庫list中移除掉
carNums--;// 總數減小1
}
return carNums;
}

public static class Car {

public String carName;// 汽車名稱
public double carPrice;// 汽車價格

public Car() {
}

public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}

/**
* 採用靜態工廠方式建立car對象,這個只是簡單模擬,不作設計模式上的過多考究
*/
public static class CarFactory {

private CarFactory() {
}

public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}

public static Car makeNewCar() {
return new Car();
}
}

/**
* 第二個版本的生產者和消費者線程,加上了同步機制的方法
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}

}

/ 採用Object類的wait和notify方法或者notifyAll方法(注意notify方法和notifyAll方法區別) // notify是喚醒其中一個在等待的線程。 // notifyAll是喚醒其餘所有在等待的線程,可是至於哪一個線程能夠得到到鎖仍是要看競爭關係。
線程狀態:建立、運行、阻塞、銷燬狀態。(阻塞狀況比較多,好比等待數據IO輸入,阻塞了。)

複製代碼代碼以下:


package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;

/**
* 第二個版本的生產者消費者線程
*
* @author pinefantasy
* @since 2013-11-1
*/
public class ThreadDemo4 {

/**
* 姑且賣車的當作是生產者線程
*/
public static class CarSeller implements Runnable {

private CarBigHouse bigHouse;

public CarSeller(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作生產者線程,往倉庫裏邊增長汽車,實際上是觸發增長汽車
int count = bigHouse.put();
System.out.println("生產汽車-->count = " + count);
}
}

}

/**
* 姑且買車的人當作是消費者線程
*/
public static class Consumer implements Runnable {

private CarBigHouse bigHouse;

public Consumer(CarBigHouse bigHouse) {
this.bigHouse = bigHouse;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {// 當作消費者線程,從倉庫裏邊提取汽車,實際上是觸發,從倉庫裏邊提取一輛汽車出來
int count = bigHouse.get();
System.out.println("消費汽車-->count = " + count);
}
}

}

/**
* 這邊姑且當作是車子big house放車子的倉庫房
*/
public static class CarBigHouse {

public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list
public static final int max = 100;// 簡單設置下,作下上限設置

private Object lock = new Object();// 採用object的wait和notify方式處理同步問題

public int put() {// 提供給生產者放汽車到倉庫的接口
synchronized (lock) {
if (carList.size() == max) {// 達到了上限,再也不生產car
try {
lock.wait();// 進行阻塞處理
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Car car = CarFactory.makeNewCar();
carList.add(car);// 加到倉庫中去
carNums++;// 總數增長1
lock.notify();// 喚醒等待的線程
return carNums;
}
}

public int get() {// 提供給消費者從這邊取汽車接口
Car car = null;
synchronized (lock) {
if (carList.size() == 0) {// 沒有汽車能夠用來消費
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (carList.size() != 0) {// size不爲空纔去取車
car = carList.get(carList.size() - 1);// 提取最後一個car
carList.remove(car);// 從從庫list中移除掉
carNums--;// 總數減小1
}
lock.notify();
return carNums;
}
}

public static class Car {

public String carName;// 汽車名稱
public double carPrice;// 汽車價格

public Car() {
}

public Car(String carName, double carPrice) {
this.carName = carName;
this.carPrice = carPrice;
}
}
}

/**
* 採用靜態工廠方式建立car對象,這個只是簡單模擬,不作設計模式上的過多考究
*/
public static class CarFactory {

private CarFactory() {
}

public static Car makeNewCar(String carName, double carPrice) {
return new Car(carName, carPrice);
}

public static Car makeNewCar() {
return new Car();
}
}

/**
* 第二個版本的生產者和消費者線程,加上了同步機制的方法
*
* @param args
*/
public static void main(String[] args) {
CarBigHouse bigHouse = new CarBigHouse();
new Thread(new CarSeller(bigHouse)).start();
new Thread(new Consumer(bigHouse)).start();
}

}

 

線程池、線程同步、互斥鎖、讀寫鎖、原子數、喚醒、通知、信號量、線程交換隊列

 

線程池

推薦用ThreadPoolExecutor的工廠構造類Executors來管理線程池,線程複用線程池開銷較每次申請新線程小,具體看代碼以及註釋

 

publicclassTestThread {

/**

* 使用線程池的方式是複用線程的(推薦)

* 而不使用線程池的方式是每次都要建立線程

* Executors.newCachedThreadPool(),該方法返回的線程池是沒有線程上限的,可能會致使過多的內存佔用

* 建議使用Executors.newFixedThreadPool(n)

*

* 有興趣還能夠看下定時線程池:SecheduledThreadPoolExecutor

*/

publicstaticvoidmain(String[] args) throwsInterruptedException, ExecutionException {

intnThreads = 5;

 

/**

* ExecutorsThreadPoolExecutor的工廠構造方法

*/

ExecutorService executor = Executors.newFixedThreadPool(nThreads);

 

//submit有返回值,而execute沒有返回值,有返回值方便Exception的處理

Future res = executor.submit(newConsumerThread());

//executor.execute(new ConsumerThread());

 

/**

* shutdown調用後,不能夠再submit新的task,已經submit的將繼續執行

* shutdownNow試圖中止當前正執行的task,並返回還沒有執行的tasklist

*/

executor.shutdown();

 

//配合shutdown使用,shutdown以後等待全部的已提交線程運行完,或者到超時。繼續執行後續代碼

executor.awaitTermination(1, TimeUnit.DAYS);

 

//打印執行結果,出錯的話會拋出異常,若是是調用execute執行線程那異常會直接拋出,很差控制,submit提交線程,調用res.get()時纔會拋出異常,方便控制異常

System.out.println("future result:"+res.get());

}

 

staticclassConsumerThread implementsRunnable{

 

@Override

publicvoidrun() {

for(inti=0;i<5;i++) {

System.out.println(i);

}

}

}

}

 

 

輸出:
0

1

2

3

4

future result:null

 

 

線程同步

synchronized(this)和synchronized(MyClass.class)區別:前者與加synchronized的成員方法互斥,後者和加synchronized的靜態方法互斥

 

synchronized的一個應用場景是單例模式的,雙重檢查鎖

 

publicclassSingleton {

privatevolatilestaticSingleton singleton;

privateSingleton (){}

publicstaticSingleton getSingleton() {

if(singleton == null) {

synchronized(Singleton.class) {

  if(singleton == null) {

  singleton = newSingleton();

  }

}

}

returnsingleton;

}

}

 

 

注意:不過雙重檢查鎖返回的實例多是沒有構造徹底的對象,高併發的時候直接使用有問題,不知道在新版的java裏是否解決了

因此有了內部類方式的單例模式,這樣的單例模式有了延遲加載的功能(還有一種枚舉方式的單例模式,用的很少,有興趣的能夠上網查)

 

//(推薦)延遲加載的單例模式
publicclassSingleton {

privatestaticclassSingletonHolder {

  privatestaticfinalSingleton INSTANCE = newSingleton();

}

privateSingleton (){}

publicstaticfinalSingleton getInstance() {

  returnSingletonHolder.INSTANCE;

}

}

 

 

若不要延遲加載,在類加載的時候實例化對象,那直接這麼寫,以下:

 

publicclassSingleton {

privatestaticSingleton instance = newSingleton();

privateSingleton (){}

publicstaticSingleton getInstance() {

  returninstance;

}

}

 

 

volatile保證同一變量在多線程中的可見性,因此它更可能是用於修飾做爲開關狀態的變量

用synchronized修飾變量的get和set方法,不但能夠保證和volatile修飾變量同樣的效果(獲取最新值),由於synchronized不只會把當前線程修改的變量的本地副本同步給主存,還會從主存中讀取數據更新本地副本。並且synchronized還有互斥的效果,能夠有效控制併發修改一個值,由於synchronized保證代碼塊的串行執行。若是隻要求獲取最新值的特性,用volatile就好,由於volatile比較輕量,性能較好。

互斥鎖、讀寫鎖

ReentrantLock 和ReentrantReadWriteLock

JDK5增長了ReentrantLock這個類由於兩點:

1.ReentrantLock提供了tryLock方法,tryLock調用的時候,若是鎖被其餘線程(同一個線程兩次調用tryLock也都返回true)持有,那麼tryLock會當即返回,返回結果是false。lock()方法會阻塞。

2.構造RenntrantLock對象能夠接收一個boolean類型的參數,描述鎖公平與否的函數。公平鎖的好處是等待鎖的線程不會餓死,可是總體效率相對低一些;非公平鎖的好處是總體效率相對高一些。

注意:使用ReentrantLock後,須要顯式地進行unlock,因此建議在finally塊中釋放鎖,以下:

 

lock.lock();try{

//do something }finally{

lock.unlock();

}

 

ReentrantReadWriteLock與ReentrantLock的用法相似,差別是前者經過readLock()和writeLock()兩個方法得到相關的讀鎖和寫鎖操做。

 

原子數

除了用互斥鎖控制變量的併發修改以外,jdk5中還增長了原子類,經過比較並交換(硬件CAS指令)來避免線程互斥等待的開銷,進而完成超輕量級的併發控制,通常用來高效的獲取遞增計數器。

AtomicInteger counter = newAtomicInteger();

counter.incrementAndGet();

counter.decrementAndGet();

能夠簡單的理解爲如下代碼,增長以後與原先值比較,若是發現增加不一致則循環這個過程。代碼以下

 

publicclassCasCounter {

privateSimulatedCAS value;

publicintgetValue() {

returnvalue.getValue();

}

publicintincrement() {

intoldValue = value.getValue();

while(value.compareAndSwap(oldValue, oldValue + 1) != oldValue)

oldValue = value.getValue();

returnoldValue + 1;

}

}

 

能夠看IBM工程師的一篇文章 Java 理論與實踐: 流行的原子

 

喚醒、通知

wait,notify,notifyAll是java的Object對象上的三個方法,多線程中能夠用這些方法完成線程間的狀態通知。

notify是喚醒一個等待線程,notifyAll會喚醒全部等待線程。

CountDownLatch主要提供的機制是當多個(具體數量等於初始化CountDownLatch時的count參數的值)線程都到達了預期狀態或完成預期工做時觸發事件,其餘線程能夠等待這個事件來觸發後續工做。

舉個例子,大數據分拆給多個線程進行排序,好比主線程

 

CountDownLatch latch = newCountDownLatch(5);

for(inti=0;i<5;i++) {

threadPool.execute(newMyRunnable(latch,datas));

}

 

latch.await();

//do something 合併數據

 

MyRunnable的實現代碼以下

publicvoidrun() {

//do something數據排序 latch.countDown();
   //繼續本身線程的工做,與CyclicBarrier最大的不一樣,稍後立刻講

}

 

CyclicBarrier循環屏障,協同多個線程,讓多個線程在這個屏障前等待,直到全部線程都到達了這個屏障時,再一塊兒繼續執行後面的動做

使用CyclicBarrier能夠重寫上面的排序代碼

主線程以下

 

CyclicBarrier barrier = newCyclicBarrier(5+1); //主線程也要消耗一個await,因此+1

for(inti=0;i<5;i++) {

threadPool.execute(newMyRunnable(barrier,datas));//若是線程池線程數過少,就會發生死鎖

}

 

barrier.await();//合併數據

 

MyRunnable代碼以下

publicvoidrun() {

//數據排序barrier.await();

}

//所有 count+1 await以後(包括主線程),以後的代碼纔會一塊兒執行

 

信號量

Semaphore用於管理信號量,與鎖的最大區別是,能夠經過令牌的數量,控制併發數量,當管理的信號量只有1個時,就退化到互斥鎖。

例如咱們須要控制遠程方法的併發量,代碼以下

 

semaphore.acquire(count);try{

//調用遠程方法 }finally{

semaphore.release(count);

}

 

 

線程交換隊列

Exchanger用於在兩個線程之間進行數據交換,線程會阻塞在Exchanger的exchange方法上,直到另一個線程也到了同一個Exchanger的exchanger方法時,兩者進行交換,而後兩個線程繼續執行自身相關代碼。

 

publicclassTestExchanger {

staticExchanger exchanger = newExchanger();

publicstaticvoidmain(String[] args) {

newThread() {

publicvoidrun() {

inta = 1;

try{

a = (int) exchanger.exchange(a);

} catch(Exception e) {

e.printStackTrace();

}

System.out.println("Thread1: "+a);

}

}.start();

 

newThread() {

publicvoidrun() {

inta = 2;

try{

a = (int) exchanger.exchange(a);

} catch(Exception e) {

e.printStackTrace();

}

System.out.println("Thread2: "+a);

}

}.start();

}

}

 

輸出結果:

Thread2: 1

Thread1: 2

 

併發容器

CopyOnWrite思路是在更改容器時,把容器寫一份進行修改,保證正在讀的線程不受影響,適合應用在讀多寫少的場景,由於寫的時候重建一次容器。

以Concurrent開頭的容器儘可能保證讀不加鎖,而且修改時不影響讀,因此會達到比使用讀寫鎖更高的併發性能

 

 

Java NIO:淺析I/O模型

 也許不少朋友在學習NIO的時候都會感受有點吃力,對裏面的不少概念都感受不是那麼明朗。在進入Java NIO編程以前,咱們今天先來討論一些比較基礎的知識:I/O模型。下面本文先從同步和異步的概念 提及,而後接着闡述了阻塞和非阻塞的區別,接着介紹了阻塞IO和非阻塞IO的區別,而後介紹了同步IO和異步IO的區別,接下來介紹了5IO模型,最後介紹了兩種和高性能IO設計相關的設計模式(ReactorProactor)。

  如下是本文的目錄大綱:

  一.什麼是同步?什麼是異步?

  二.什麼是阻塞?什麼是非阻塞?

  三.什麼是阻塞IO?什麼是非阻塞IO

  四.什麼是同步IO?什麼是異步IO

  五.五種IO模型

  六.兩種高性能IO設計模式

  如有不正之處,請多多諒解並歡迎批評指正。

  請尊重做者勞動成果,轉載請標明原文連接:

http://www.cnblogs.com/dolphin0520/p/3916526.html

一.什麼是同步?什麼是異步?

  同步和異步的概念出來已經好久了,網上有關同步和異步的說法也有不少。如下是我我的的理解:

  同步就是:若是有多個任務或者事件要發生,這些任務或者事件必須逐個地進行,一個事件或者任務的執行會致使整個流程的暫時等待,這些事件沒有辦法併發地執行;

  異步就是:若是有多個任務或者事件發生,這些事件能夠併發地執行,一個事件或者任務的執行不會致使整個流程的暫時等待。

  這就是同步和異步。舉個簡單的例子,假若有一個任務包括兩個子任務AB,對於同步來講,當A在執行的過程當中,B只有等待,直至A執行完畢,B才能執行;而對於異步就是AB能夠併發地執行,B沒必要等待A執行完畢以後再執行,這樣就不會因爲A的執行致使整個任務的暫時等待。

  若是還不理解,能夠先看下面這2段代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

voidfun1() {

 

}

 

voidfun2() {

 

}

 

voidfunction(){

fun1();

fun2()

.....

.....

}

  這段代碼就是典型的同步,在方法function中,fun1在執行的過程當中會致使後續的fun2沒法執行,fun2必須等待fun1執行完畢才能夠執行。

  接着看下面這段代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

voidfun1() {

 

}

 

voidfun2() {

 

}

 

voidfunction(){

newThread(){

publicvoidrun() {

fun1();

}

}.start();

 

newThread(){

publicvoidrun() {

fun2();

}

}.start();

 

.....

.....

}

  這段代碼是一種典型的異步,fun1的執行不會影響到fun2的執行,而且fun1fun2的執行不會致使其後續的執行過程處於暫時的等待。

  事實上,同步和異步是一個很是廣的概念,它們的重點在於多個任務和事件發生時,一個事件的發生或執行是否會致使整個流程的暫時等待。我以爲能夠將同步和異步與Java中的synchronized關鍵字聯繫起來進行類比。當多個線程同時訪問一個變量時,每一個線程訪問該變量就是一個事件,對於同步來講,就是這些線程必須逐個地來訪問該變量,一個線程在訪問該變量的過程當中,其餘線程必須等待;而對於異步來講,就是多個線程沒必要逐個地訪問該變量,能夠同時進行訪問。

  所以,我的以爲同步和異步能夠表如今不少方面,可是記住其關鍵在於多個任務和事件發生時,一個事件的發生或執行是否會致使整個流程的暫時等待。通常來講,能夠經過多線程的方式來實現異步,可是千萬記住不要將多線程和異步畫上等號,異步只是宏觀上的一個模式,採用多線程來實現異步只是一種手段,而且經過多進程的方式也能夠實現異步。

二.什麼是阻塞?什麼是非阻塞?

  在前面介紹了同步和異步的區別,這一節來看一下阻塞和非阻塞的區別。

  阻塞就是:當某個事件或者任務在執行過程當中,它發出一個請求操做,可是因爲該請求操做須要的條件不知足,那麼就會一直在那等待,直至條件知足;

  非阻塞就是:當某個事件或者任務在執行過程當中,它發出一個請求操做,若是該請求操做須要的條件不知足,會當即返回一個標誌信息告知條件不知足,不會一直在那等待。

  這就是阻塞和非阻塞的區別。也就是說阻塞和非阻塞的區別關鍵在於當發出請求一個操做時,若是條件不知足,是會一直等待仍是返回一個標誌信息。

  舉個簡單的例子:

  假如我要讀取一個文件中的內容,若是此時文件中沒有內容可讀,對於同步來講就是會一直在那等待,直至文件中有內容可讀;而對於非阻塞來講,就會直接返回一個標誌信息告知文件中暫時無內容可讀。

  在網上有一些朋友將同步和異步分別與阻塞和非阻塞畫上等號,事實上,它們是兩組徹底不一樣的概念。注意,理解這兩組概念的區別對於後面IO模型的理解很是重要。

  同步和異步着重點在於多個任務的執行過程當中,一個任務的執行是否會致使整個流程的暫時等待;

  而阻塞和非阻塞着重點在於發出一個請求操做時,若是進行操做的條件不知足是否會返會一個標誌信息告知條件不知足。

  理解阻塞和非阻塞能夠同線程阻塞類比地理解,當一個線程進行一個請求操做時,若是條件不知足,則會被阻塞,即在那等待條件知足。

三.什麼是阻塞IO?什麼是非阻塞IO?

  在瞭解阻塞IO和非阻塞IO以前,先看下一個具體的IO操做過程是怎麼進行的。

  一般來講,IO操做包括:對硬盤的讀寫、對socket的讀寫以及外設的讀寫。

  當用戶線程發起一個IO請求操做(本文以讀請求操做爲例),內核會去查看要讀取的數據是否就緒,對於阻塞IO來講,若是數據沒有就緒,則會一直在那等待,直到數據就緒;對於非阻塞IO來講,若是數據沒有就緒,則會返回一個標誌信息告知用戶線程當前要讀的數據沒有就緒。當數據就緒以後,便將數據拷貝到用戶線程,這樣才完成了一個完整的IO讀請求操做,也就是說一個完整的IO讀請求操做包括兩個階段:

  1)查看數據是否就緒;

  2)進行數據拷貝(內核將數據拷貝到用戶線程)。

  那麼阻塞(blocking IO)和非阻塞(non-blocking IO)的區別就在於第一個階段,若是數據沒有就緒,在查看數據是否就緒的過程當中是一直等待,仍是直接返回一個標誌信息。

  Java中傳統的IO都是阻塞IO,好比經過socket來讀數據,調用read()方法以後,若是數據沒有就緒,當前線程就會一直阻塞在read方法調用那裏,直到有數據才返回;而若是是非阻塞IO的話,當數據沒有就緒,read()方法應該返回一個標誌信息,告知當前線程數據沒有就緒,而不是一直在那裏等待。

四.什麼是同步IO?什麼是異步IO?

  咱們先來看一下同步IO和異步IO的定義,在《Unix網絡編程》一書中對同步IO和異步IO的定義是這樣的:

  A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
  An asynchronous I/O operation does not cause the requesting process to be blocked.

  從字面的意思能夠看出:同步IO即 若是一個線程請求進行IO操做,在IO操做完成以前,該線程會被阻塞;

  而異步IO爲 若是一個線程請求進行IO操做,IO操做不會致使請求線程被阻塞。

  事實上,同步IO和異步IO模型是針對用戶線程和內核的交互來講的:

  對於同步IO:當用戶發出IO請求操做以後,若是數據沒有就緒,須要經過用戶線程或者內核不斷地去輪詢數據是否就緒,當數據就緒時,再將數據從內核拷貝到用戶線程;

  而異步IO:只有IO請求操做的發出是由用戶線程來進行的,IO操做的兩個階段都是由內核自動完成,而後發送通知告知用戶線程IO操做已經完成。也就是說在異步IO中,不會對用戶線程產生任何阻塞。

  這是同步IO和異步IO關鍵區別所在,同步IO和異步IO的關鍵區別反映在數據拷貝階段是由用戶線程完成仍是內核完成。因此說異步IO必需要有操做系統的底層支持。

  注意同步IO和異步IO與阻塞IO和非阻塞IO是不一樣的兩組概念。

  阻塞IO和非阻塞IO是反映在當用戶請求IO操做時,若是數據沒有就緒,是用戶線程一直等待數據就緒,仍是會收到一個標誌信息這一點上面的。也就是說,阻塞IO和非阻塞IO是反映在IO操做的第一個階段,在查看數據是否就緒時是如何處理的。

五.五種IO模型

  在《Unix網絡編程》一書中提到了五種IO模型,分別是:阻塞IO、非阻塞IO、多路複用IO、信號驅動IO以及異步IO

  下面就分別來介紹一下這5IO模型的異同。

1.阻塞IO模型

  最傳統的一種IO模型,即在讀寫數據過程當中會發生阻塞現象。

  當用戶線程發出IO請求以後,內核會去查看數據是否就緒,若是沒有就緒就會等待數據就緒,而用戶線程就會處於阻塞狀態,用戶線程交出CPU。當數據就緒以後,內核會將數據拷貝到用戶線程,並返回結果給用戶線程,用戶線程才解除block狀態。

  典型的阻塞IO模型的例子爲:

1

data = socket.read();

  若是數據沒有就緒,就會一直阻塞在read方法。

2.非阻塞IO模型

  當用戶線程發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。若是結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦內核中的數據準備好了,而且又再次收到了用戶線程的請求,那麼它立刻就將數據拷貝到了用戶線程,而後返回。

  因此事實上,在非阻塞IO模型中,用戶線程須要不斷地詢問內核數據是否就緒,也就說非阻塞IO不會交出CPU,而會一直佔用CPU

  典型的非阻塞IO模型通常以下:

1

2

3

4

5

6

7

while(true){

data = socket.read();

if(data!= error){

處理數據

break;

}

}

  可是對於非阻塞IO就有一個很是嚴重的問題,在while循環中須要不斷地去詢問內核數據是否就緒,這樣會致使CPU佔用率很是高,所以通常狀況下不多使用while循環這種方式來讀取數據。

3.多路複用IO模型

  多路複用IO模型是目前使用得比較多的模型。Java NIO實際上就是多路複用IO

  在多路複用IO模型中,會有一個線程不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正調用實際的IO讀寫操做。由於在多路複用IO模型中,只須要使用一個線程就能夠管理多個socket,系統不須要創建新的進程或者線程,也沒必要維護這些線程和進程,而且只有在真正有socket讀寫事件進行時,纔會使用IO資源,因此它大大減小了資源佔用。

  在Java NIO中,是經過selector.select()去查詢每一個通道是否有到達事件,若是沒有事件,則一直阻塞在那裏,所以這種方式會致使用戶線程的阻塞。

  也許有朋友會說,我能夠採用 多線程+ 阻塞IO 達到相似的效果,可是因爲在多線程 + 阻塞IO 中,每一個socket對應一個線程,這樣會形成很大的資源佔用,而且尤爲是對於長鏈接來講,線程的資源一直不會釋放,若是後面陸續有不少鏈接的話,就會形成性能上的瓶頸。

  而多路複用IO模式,經過一個線程就能夠管理多個socket,只有當socket真正有讀寫事件發生纔會佔用資源來進行實際的讀寫操做。所以,多路複用IO比較適合鏈接數比較多的狀況。

  另外多路複用IO爲什麼比非阻塞IO模型的效率高是由於在非阻塞IO中,不斷地詢問socket狀態時經過用戶線程去進行的,而在多路複用IO中,輪詢每一個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。

  不過要注意的是,多路複用IO模型是經過輪詢的方式來檢測是否有事件到達,而且對到達的事件逐一進行響應。所以對於多路複用IO模型來講,一旦事件響應體很大,那麼就會致使後續的事件遲遲得不處處理,而且會影響新的事件輪詢。

4.信號驅動IO模型

  在信號驅動IO模型中,當用戶線程發起一個IO請求操做,會給對應的socket註冊一個信號函數,而後用戶線程會繼續執行,當內核數據就緒時會發送一個信號給用戶線程,用戶線程接收到信號以後,便在信號函數中調用IO讀寫操做來進行實際的IO請求操做。

5.異步IO模型

  異步IO模型纔是最理想的IO模型,在異步IO模型中,當用戶線程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從內核的角度,當它受到一個asynchronous read以後,它會馬上返回,說明read請求已經成功發起了,所以不會對用戶線程產生任何block。而後,內核會等待數據準備完成,而後將數據拷貝到用戶線程,當這一切都完成以後,內核會給用戶線程發送一個信號,告訴它read操做完成了。也就說用戶線程徹底不須要實際的整個IO操做是如何進行的,只須要先發起一個請求,當接收內核返回的成功信號時表示IO操做已經完成,能夠直接去使用數據了。

  也就說在異步IO模型中,IO操做的兩個階段都不會阻塞用戶線程,這兩個階段都是由內核自動完成,而後發送一個信號告知用戶線程操做已完成。用戶線程中不須要再次調用IO函數進行具體的讀寫。這點是和信號驅動模型有所不一樣的,在信號驅動模型中,當用戶線程接收到信號表示數據已經就緒,而後須要用戶線程調用IO函數進行實際的讀寫操做;而在異步IO模型中,收到信號表示IO操做已經完成,不須要再在用戶線程中調用iO函數進行實際的讀寫操做。

  注意,異步IO是須要操做系統的底層支持,在Java 7中,提供了Asynchronous IO

  前面四種IO模型實際上都屬於同步IO,只有最後一種是真正的異步IO,由於不管是多路複用IO仍是信號驅動模型,IO操做的第2個階段都會引發用戶線程阻塞,也就是內核進行數據拷貝的過程都會讓用戶線程阻塞。

六.兩種高性能IO設計模式

  在傳統的網絡服務設計模式中,有兩種比較經典的模式:

  一種是 多線程,一種是線程池。

  對於多線程模式,也就說來了client,服務器就會新建一個線程來處理該client的讀寫事件,以下圖所示:

 

  這種模式雖然處理起來簡單方便,可是因爲服務器爲每一個client的鏈接都採用一個線程去處理,使得資源佔用很是大。所以,當鏈接數量達到上限時,再有用戶請求鏈接,直接會致使資源瓶頸,嚴重的可能會直接致使服務器崩潰。

  所以,爲了解決這種一個線程對應一個客戶端模式帶來的問題,提出了採用線程池的方式,也就說建立一個固定大小的線程池,來一個客戶端,就從線程池取一個空閒線程來處理,當客戶端處理完讀寫操做以後,就交出對線程的佔用。所以這樣就避免爲每個客戶端都要建立線程帶來的資源浪費,使得線程能夠重用。

  可是線程池也有它的弊端,若是鏈接大可能是長鏈接,所以可能會致使在一段時間內,線程池中的線程都被佔用,那麼當再有用戶請求鏈接時,因爲沒有可用的空閒線程來處理,就會致使客戶端鏈接失敗,從而影響用戶體驗。所以,線程池比較適合大量的短鏈接應用。

  所以便出現了下面的兩種高性能IO設計模式:ReactorProactor

  在Reactor模式中,會先對每一個client註冊感興趣的事件,而後有一個線程專門去輪詢每一個client是否有事件發生,當有事件發生時,便順序處理每一個事件,當全部事件處理完以後,便再轉去繼續輪詢,以下圖所示:

 

  從這裏能夠看出,上面的五種IO模型中的多路複用IO就是採用Reactor模式。注意,上面的圖中展現的 是順序處理每一個事件,固然爲了提升事件處理速度,能夠經過多線程或者線程池的方式來處理事件。

  在Proactor模式中,當檢測到有事件發生時,會新起一個異步操做,而後交由內核線程去處理,當內核線程完成IO操做以後,發送一個通知告知操做已完成,能夠得知,異步IO模型採用的就是Proactor模式。

今天剛剛看完javaio流操做,把主要的脈絡看了一遍,不能保證之後使用時都能駕輕就熟,可是最起碼用到時知道有這麼一個功能能夠實現,下面對學習進行一下簡單的總結:

IO流主要用於硬板、內存、鍵盤等處理設備上得數據操做,根據處理數據的數據類型的不一樣能夠分爲:字節流(抽象基類爲InPutStreamOutPutStream)和字符流(抽象基類爲ReaderWriter)。根據流向不一樣,能夠分爲:輸入流和輸出流。 其中主要結構能夠用下圖來表示:

 

 

字符流和字節流的主要區別:

1.字節流讀取的時候,讀到一個字節就返回一個字節; 字符流使用了字節流讀到一個或多個字節(中文對應的字節數是兩個,在UTF-8碼錶中是3個字節)時。先去查指定的編碼表,將查到的字符返回。

2.字節流能夠處理全部類型數據,如:圖片,MP3AVI視頻文件,而字符流只能處理字符數據。只要是處理純文本數據,就要優先考慮使用字符流,除此以外都用字節流。

 

IO流主要能夠分爲節點流和處理流兩大類。

java:IO流學習小結

 

1、節點流類型

該類型能夠從或者向一個特定的地點或者節點讀寫數據。主要類型以下:

類型

字符流

字節流

File(文件)

FileReader

FileWriter

FileInputStream

FileOutputSream

Memory Array

CharArrayReader

CharArrayWriter

ByteArrayInputStream

ByteArrayOutputSream

Memory String

StringReader

StringWriter

-

Pipe(管道)

PipedReader

PipedWriter

PipedInputSream

PipedOutputSream

 

2、處理流類型

該類型是對一個已存在的流的鏈接和封裝,經過所封裝的流的功能調用實現數據讀寫,處理流的構造方法老是要帶一個其餘流對象做爲參數,一個流對象進過其餘流的屢次包裝,叫作流的連接。主要能夠分爲如下幾種:

 

1、緩衝流(BufferedInPutStream/BufferedOutPutStreamBufferedWriter/BufferedReader他能夠提升對流的操做效率。

 

寫入緩衝區對象:

[java]view plaincopy

1.BufferedWriterbufw=newBufferedWriter(newFileWriter("buf.txt"));

 

讀取緩衝區對象:

[java]view plaincopy

1.BufferedReaderbufr=newBufferedReader(newFileReader("buf.txt"));

 

該類型的流有一個特有的方法:readLine();一次讀一行,到行標記時,將行標記以前的字符數據做爲字符串返回,當讀到末尾時,返回null,其原理仍是與緩衝區關聯的流對象的read方法,只不過每一次讀取到一個字符,先不進行具體操做,先進行臨時儲存,當讀取到回車標記時,將臨時容器中儲存的數據一次性返回。

 

 

2、轉換流(InputStreamReader/OutputStreamWriter

該類型時字節流和字符流之間的橋樑,該流對象中能夠對讀取到的字節數據進行指定編碼的編碼轉換。

構造函數主要有:

[java]view plaincopy

1.InputStreamReader(InputStream);//經過構造函數初始化,使用的是本系統默認的編碼表GBK。

2.InputStreamWriter(InputStream,StringcharSet);//經過該構造函數初始化,能夠指定編碼表。

3.OutputStreamWriter(OutputStream);//經過該構造函數初始化,使用的是本系統默認的編碼表GBK。

4.OutputStreamwriter(OutputStream,StringcharSet);//經過該構造函數初始化,能夠指定編碼表。

注意:在使用FileReader操做文本數據時,該對象使用的時默認的編碼表,即

FileReader fr=new FileReader(「a.txt」); InputStreamReader isr=new InputStreamReader(new FileInputStream("a.txt")); 的意義相同。若是要使用指定表編碼表時,必須使用轉換流,即若是a.txt中的文件中的字符數據是經過utf-8的形式編碼,那麼在讀取時,就必須指定編碼表,那麼轉換流時必須的。即:

InputStreamReader isr=new InputStreamReader(new FileInputStream("a.txt"),utf-8);

3、數據流(DataInputStream/DataOutputStream

該數據流能夠方便地對一些基本類型數據進行直接的存儲和讀取,不須要再進一步進行轉換,一般只要操做基本數據類型的數據,就須要經過DataStream進行包裝。

構造方法:

[java]view plaincopy

1.DataInputStreamReader(InputStream);

2.DataInputStreamWriter(OutputStream);

方法舉例:

[java]view plaincopy

1.intreadInt();//一次讀取四個字節,並將其轉成int值

2.writeInt(int);//一次寫入四個字節,注意和write(int)不一樣,write(int)只將該整數的最低一個8位寫入,剩餘三個8爲丟失

3.hortreadShort();

4.writeShort(short);

5.StringreadUTF();//按照utf-8修改版讀取字符,注意,它只能讀writeUTF()寫入的字符數據。

6.writeUTF(String);//按照utf-8修改版將字符數據進行存儲,只能經過readUTF讀取。

注意:在使用數據流讀/存數據的時候,須要有必定的順序,即某個類型的數據先寫入就必須先讀出,服從先進先出的原則。

 

 

4、打印流(PrintStream/PrintWriter

PrintStream是一個字節打印流,System.out對應的類型就是PrintStream,它的構造函數能夠接受三種數據類型的值:1.字符串路徑。2.File對象 3.OutputStream

PrintStream是一個字符打印流,它的構造函數能夠接受四種類型的值:1.字符串路徑。2.File對象 3.OutputStream 4.Writer 對於12類型的數據,能夠指定編碼表,也就是字符集,對於34類型的數據,能夠指定自動刷新,當該自動刷新爲True時,只有3個方法能夠用:println,printf,format

 

5、對象流(ObjectInputStream/ObjectOutputStream

該類型的流能夠把類做爲一個總體進行存取,主要方法有:

Object readObject();該方法拋出異常:ClassNotFountException

void writeObject(Object):被寫入的對象必須實現一個接口:Serializable,不然就會拋出:NotSerializableException

java Socket用法詳解

在客戶/服務器通訊模式中, 客戶端須要主動建立與服務器鏈接的 Socket(套接字), 服務器端收到了客戶端的鏈接請求, 也會建立與客戶鏈接的 Socket. Socket可看作是通訊鏈接兩端的收發器, 服務器與客戶端都經過 Socket 來收發數據.

這篇文章首先介紹Socket類的各個構造方法, 以及成員方法的用法, 接着介紹 Socket的一些選項的做用, 這些選項可控制客戶創建與服務器的鏈接, 以及接收和發送數據的行爲.

. 構造Socket

Socket的構造方法有如下幾種重載形式:

Socket()
Socket(InetAddress address, int port) throws UnknowHostException, IOException
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
Socket(String host, int port) throws UnknowHostException, IOException
Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
除了第一個不帶參數的構造方法之外, 其餘構造方法都會試圖創建與服務器的鏈接, 若是鏈接成功, 就返回 Socket對象; 若是由於某些緣由鏈接失敗, 就會拋出IOException .

1.1 使用無參數構造方法, 設定等待創建鏈接的超時時間

Socket socket = new Socket();
SocketAddress remoteAddr = new InetSocketAddress("localhost",8000);
socket.connect(remoteAddr, 60000); //等待創建鏈接的超時時間爲1分鐘

以上代碼用於鏈接到本地機器上的監聽8000端口的服務器程序, 等待鏈接的最長時間爲1分鐘. 若是在1分鐘內鏈接成功則connet()方法順利返回; 若是在1分鐘內出現某種異常, 則拋出該異常; 若是超過1分鐘後, 即沒有鏈接成功, 也沒有出現其餘異常, 那麼會拋出 SocketTimeoutException. Socket 類的 connect(SocketAddress endpoint, int timeout) 方法負責鏈接服務器, 參數endpoint 指定服務器的地址, 參數timeout 設定超時數據, 以毫秒爲單位. 若是參數timeout 設爲0, 表示永遠不會超時, 默認是不會超時的.

1.2 設定服務器的地址

除了第一個不帶參數的構造方法, 其餘構造方法都須要在參數中設定服務器的地址, 包括服務器的IP地址或主機名, 以及端口:

Socket(InetAddress address, int port) //第一個參數address 表示主機的IP地址
Socket(String host, int port) //第一個參數host 表示主機的名字

InetAddress 類表示服務器的IP地址, InetAddress 類提供了一系列靜態工廠方法, 用於構造自身的實例, 例如:

//返回本地主機的IP地址
InetAddress addr1 = InetAddress.getLocalHost();
//返回表明 "222.34.5.7"IP地址
InetAddress addr2 = InetAddress.getByName("222.34.5.7");
//返回域名爲"www.javathinker.org"IP地址
InetAddress addr3 = InetAddress.getByName("www.javathinker.org");

1.3 設定客戶端的地址

在一個Socket 對象中, 即包含遠程服務器的IP 地址和端口信息, 也包含本地客戶端的IP 地址和端口信息. 默認狀況下, 客戶端的IP 地址來自於客戶程序所在的主機, 客戶端的端口則由操做系統隨機分配. Socket類還有兩個構造方法容許顯式地設置客戶端的IP 地址和端口:

//參數localAddr localPort 用來設置客戶端的IP 地址和端口
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException

若是一個主機同時屬於兩個以上的網絡, 它就可能擁有兩個以上的IP 地址. 例如, 一個主機在Internet 網絡中的IP 地址爲 "222.67.1.34", 在一個局域網中的IP 地址爲 "112.5.4.3". 假定這個主機上的客戶程序但願和同一個局域網的一個服務器程序(地址爲:"112.5.4.45: 8000")通訊, 客戶端可按照以下方式構造Socket 對象:

InetAddress remoteAddr1 = InetAddress.getByName("112.5.4.45");
InetAddress localAddr1 = InetAddress.getByName("112.5.4.3");
Socket socket1 = new Socket(remoteAddr1, 8000, localAddr1, 2345); //客戶端使用端口2345

1.4 客戶鏈接服務器時可能拋出的異常

Socket 的構造方法請求鏈接服務器時, 可能會拋出下面的異常.

UnKnownHostException: 若是沒法識別主機的名字或IP 地址, 就會拋出這種異常.
ConnectException: 若是沒有服務器進程監聽指定的端口, 或者服務器進程拒絕鏈接, 就會拋出這種異常.
SocketTimeoutException: 若是等待鏈接超時, 就會拋出這種異常.
BindException: 若是沒法把Socket 對象與指定的本地IP 地址或端口綁定, 就會拋出這種異常.
以上4中異常都是IOException的直接或間接子類. 如圖2-1所示.

IOException------- UnknownHostException

|---- InterruptedIOException ----------- SocketTimeoutException

|---- SocketException ----------- BindException

|---------- ConnectException

 

2-1 客戶端鏈接服務器時可能拋出的異常

. 獲取Socket 的信息

在一個Socket 對象中同時包含了遠程服務器的IP 地址和端口信息, 以及客戶本地的IP 地址和端口信息. 此外, Socket 對象中還能夠得到輸出流和輸入流, 分別用於向服務器發送數據, 以及接收從服務器端發來的數據. 如下方法用於獲取Socket的有關信息.

getInetAddress(): 得到遠程服務器的IP 地址.
getPort(): 得到遠程服務器的端口.
getLocalAddress(): 得到客戶本地的IP 地址.
getLocalPort(): 得到客戶本地的端口.
getInputStream(): 得到輸入流. 若是Socket 尚未鏈接, 或者已經關閉, 或者已經經過 shutdownInput() 方法關閉輸入流, 那麼此方法會拋出IOException.
getOutputStream(): 得到輸出流, 若是Socket 尚未鏈接, 或者已經關閉, 或者已經經過 shutdownOutput() 方法關閉輸出流, 那麼此方法會拋出IOException.
這裏有個HTTPClient 類的例子, 代碼我是寫好了, 也測試過了, 由於篇幅緣由就不貼了. 這個HTTPClient 類用於訪問網頁www.javathinker.org/index.jsp. 該網頁位於一個主機名(也叫域名)www.javathinker.org的遠程HTTP服務器上, 它監聽 80 端口. HTTPClient 類中, 先建立了一個鏈接到該HTTP服務器的Socket對象, 而後發送符合HTTP 協議的請求, 接着接收從HTTP 服務器上發回的響應結果.

. 關閉Socket

當客戶與服務器的通訊結束, 應該及時關閉Socket , 以釋放Socket 佔用的包括端口在內的各類資源. Socket close() 方法負責關閉Socket. 當一個Socket對象被關閉, 就不能再經過它的輸入流和輸出流進行I/O操做, 不然會致使IOException.

爲了確保關閉Socket 的操做老是被執行, 強烈建議把這個操做放在finally 代碼塊中:

Socket socket = null;
try{
socket = new Socket(www.javathinker.org,80);
//執行接收和發送數據的操做
..........
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(socket != null) socket.close();
}catch(IOException e){e.printStackTrace();}
}

 

Socket 類提供了3 個狀態測試方法.

isClosed(): 若是Socket已經鏈接到遠程主機, 而且尚未關閉, 則返回true , 不然返回false .
isConnected(): 若是Socket曾經鏈接到遠程主機, 則返回true , 不然返回false .
isBound(): 若是Socket已經與一個本地端口綁定, 則返回true , 不然返回false .
若是要判斷一個Socket 對象當前是否處於鏈接狀態, 可採用如下方式:

boolean isConnected = socket.isConnected() && !socket.isClosed();

. 半關閉Socket

進程A 與進程B 經過Socket 通訊, 假定進程A 輸出數據, 進程B 讀入數據. 進程A 如何告訴進程B 全部數據已經輸出完畢? 下文略......

. 設置Socket 的選項

Socket 有如下幾個選項.

TCP_NODELAY: 表示當即發送數據.
SO_RESUSEADDR: 表示是否容許重用Socket 所綁定的本地地址.
SO_TIMEOUT: 表示接收數據時的等待超時數據.
SO_LINGER: 表示當執行Socket close()方法時, 是否當即關閉底層的Socket.
SO_SNFBUF: 表示發送數據的緩衝區的大小.
SO_RCVBUF: 表示接收數據的緩衝區的大小.
SO_KEEPALIVE: 表示對於長時間處於空閒狀態的Socket , 是否要自動把它關閉.
OOBINLINE: 表示是否支持發送一個字節的TCP 緊急數據.
5.1 TCP_NODELAY 選項

設置該選項: public void setTcpNoDelay(boolean on) throws SocketException
讀取該選項: public boolean getTcpNoDelay() throws SocketException
默認狀況下, 發送數據採用Negale 算法. Negale 算法是指發送方發送的數據不會當即發出, 而是先放在緩衝區, 等緩存區滿了再發出. 發送完一批數據後, 會等待接收方對這批數據的迴應, 而後再發送下一批數據. Negale 算法適用於發送方須要發送大批量數據, 而且接收方會及時做出迴應的場合, 這種算法經過減小傳輸數據的次數來提升通訊效率.

若是發送方持續地發送小批量的數據, 而且接收方不必定會當即發送響應數據, 那麼Negale 算法會使發送方運行很慢. 對於GUI 程序, 如網絡遊戲程序(服務器須要實時跟蹤客戶端鼠標的移動), 這個問題尤爲突出. 客戶端鼠標位置改動的信息須要實時發送到服務器上, 因爲Negale 算法採用緩衝, 大大減低了實時響應速度, 致使客戶程序運行很慢.

TCP_NODELAY 的默認值爲 false, 表示採用 Negale 算法. 若是調用setTcpNoDelay(true)方法, 就會關閉 Socket的緩衝, 確保數據及時發送:

if(!socket.getTcpNoDelay()) socket.setTcpNoDelay(true);

若是Socket 的底層實現不支持TCP_NODELAY 選項, 那麼getTcpNoDelay() setTcpNoDelay 方法會拋出 SocketException.

5.2 SO_RESUSEADDR 選項

設置該選項: public void setResuseAddress(boolean on) throws SocketException
讀取該選項: public boolean getResuseAddress() throws SocketException
當接收方經過Socket close() 方法關閉Socket , 若是網絡上還有發送到這個Socket 的數據, 那麼底層的Socket 不會當即釋放本地端口, 而是會等待一段時間, 確保接收到了網絡上發送過來的延遲數據, 而後再釋放端口. Socket接收到延遲數據後, 不會對這些數據做任何處理. Socket 接收延遲數據的目的是, 確保這些數據不會被其餘碰巧綁定到一樣端口的新進程接收到.

客戶程序通常採用隨機端口, 所以出現兩個客戶程序綁定到一樣端口的可能性不大. 許多服務器程序都使用固定的端口. 當服務器程序關閉後, 有可能它的端口還會被佔用一段時間, 若是此時馬上在同一個主機上重啓服務器程序, 因爲端口已經被佔用, 使得服務器程序沒法綁定到該端口, 啓動失敗. (第三篇文章會對此做出介紹).

爲了確保一個進程關閉Socket , 即便它還沒釋放端口, 同一個主機上的其餘進程還能夠當即重用該端口, 能夠調用Socket setResuseAddress(true) 方法:

if(!socket.getResuseAddress()) socket.setResuseAddress(true);

值得注意的是 socket.setResuseAddress(true) 方法必須在 Socket 尚未綁定到一個本地端口以前調用, 不然執行 socket.setResuseAddress(true) 方法無效. 所以必須按照如下方式建立Socket 對象, 而後再鏈接遠程服務器:

 

Socket socket = new Socket(); //此時Socket對象未綁定本地端口,而且未鏈接遠程服務器
socket.setReuseAddress(true);
SocketAddress remoteAddr = new InetSocketAddress("localhost",8000);
socket.connect(remoteAddr); //鏈接遠程服務器, 而且綁定匿名的本地端口

或者:

Socket socket = new Socket(); //此時Socke 對象爲綁定本地端口, 而且未鏈接遠程服務器
socket.setReuseAddress(true);
SocketAddress localAddr = new InetSocketAddress("localhost",9000);
SocketAddress remoteAddr = new InetSocketAddress("localhost",8000);
socket.bind(localAddr); //與本地端口綁定
socket.connect(remoteAddr); //鏈接遠程服務器

此外, 兩個共用同一個端口的進程必須都調用 socket.setResuseAddress(true) 方法, 才能使得一個進程關閉 Socket, 另外一個進程的 Socket 可以當即重用相同端口.

5.3 SO_TIMEOUT 選項

設置該選項: public void setSoTimeout(int milliseconds) throws SocketException
讀取該選項: public int getSoTimeout() throws SocketException
當經過Socket 的輸入流讀數據時, 若是尚未數據, 就會等待. 例如, 在如下代碼中, in.read(buff) 方法從輸入流中讀入 1024個字節:

byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
in.read(buff);

若是輸入流中沒有數據, in.read(buff) 就會等待發送方發送數據, 直到知足如下狀況才結束等待:

...............

Socket 類的 SO_TIMEOUT 選項用於設定接收數據的等待超時時間, 單位爲毫秒, 它的默認值爲 0, 表示會無限等待, 永遠不會超時. 如下代碼把接收數據的等待超時時間設爲 3 分鐘:

if(socket.getSoTimeout() == 0) socket.setSoTimeout(60000 * 3); //注意, 原書中這裏的代碼錯誤, 裏面的方法名字都少了"So"

Socket setSoTimeout() 方法必須在接收數據以前執行纔有效. 此外, 當輸入流的 read()方法拋出 SocketTimeoutException , Socket 仍然是鏈接的, 能夠嘗試再次讀數據:

socket.setSoTimeout(180000);
byte[] buff = new byte[1024];
InputStream in = socket.getInputStream();
int len = -1;
do{
try{
len = in.read(buff);
//處理讀到的數據
//.........
}catch(SocketTimeoutException e){
//e.printStackTrace();
System.out.println("等待讀超時!");
len = 0;
}
}while(len != -1);

例子ReceiveServer.java SendClient.java 是一對簡單的服務器/客戶程序. sendClient 發送字符串 "hello everyone" ,接着睡眠 1 分鐘, 而後關閉 Socket. ReceiveServer 讀取 SendClient 發送來的數據, 直到抵達輸入流的末尾, 最後打印 SendClient 發送來的數據.

ReceiveServer.java ....... , SendClient.java ..........

SendClient 發送字符串 "hello everyone" , 睡眠 1 分鐘. SendClient 在睡眠時, ReceiveServer 在執行 in.read(buff) 方法, 不能讀到足夠的數據填滿 buff 緩衝區, 所以會一直等待 SendClient 發送數據. 若是在 ReceiveServer 類中 socket.setSoTimeout(20000) , 從而把等待接收數據的超時時間設爲 20 , 那麼 ReceiveServer 在等待數據時, 每當超過 20 , 就會拋出SocketTimeoutException . 等到 SendClient 睡眠 1 分鐘後, SendClient 調用 Socket close() 方法關閉 Socket, 這意味着 ReceiveServer 讀到了輸入流的末尾, ReceiveServer 當即結束讀等待, read() 方法返回 -1 . ReceiveServer最後打印接收到的字符串 "hello everyone", 結果以下:

等待讀超時!
等待讀超時!
hello everyone

5.4 SO_LINGER 選項

設置該選項: public void setSoLinger(boolean on, int seconds) throws SocketException
讀取該選項: public int getSoLinger() throws SocketException
SO_LINGER 選項用來控制 Socket 關閉時的行爲. 默認狀況下, 執行 Socket close() 方法, 該方法會當即返回, 但底層的 Socket 實際上並不當即關閉, 它會延遲一段時間, 直到發送完全部剩餘的數據, 纔會真正關閉 Socket, 斷開鏈接.

若是執行如下方法:

socket.setSoLinger(true, 0);

那麼執行Socket close() 方法, 該方法也會當即返回, 而且底層的 Socket 也會當即關閉, 全部未發送完的剩餘數據被丟棄.

若是執行如下方法:

socket.setSoLinger(true, 3600);

那麼執行Socket close() 方法, 該方法不會當即返回, 而是進入阻塞狀態. 同時, 底層的 Socket 會嘗試發送剩餘的數據. 只有知足如下兩個條件之一, close() 方法才返回:

底層的 Socket 已經發送完全部的剩餘數據;

儘管底層的 Socket 尚未發送完全部的剩餘數據, 但已經阻塞了 3600 (注意這裏是秒, 而非毫秒), close() 方法的阻塞時間超過 3600 , 也會返回, 剩餘未發送的數據被丟棄.

值得注意的是, 在以上兩種狀況內, close() 方法返回後, 底層的 Socket 會被關閉, 斷開鏈接. 此外, setSoLinger(boolean on, int seconds) 方法中的 seconds 參數以秒爲單位, 而不是以毫秒爲單位.

若是未設置 SO_LINGER 選項, getSoLinger() 返回的結果是 -1, 若是設置了 socket.setSoLinger(true, 80) , getSoLinger() 返回的結果是 80.

Tips: 當程序經過輸出流寫數據時, 僅僅表示程序向網絡提交了一批數據, 由網絡負責輸送到接收方. 當程序關閉 Socket, 有可能這批數據還在網絡上傳輸, 還未到達接收方. 這裏所說的 "未發送完的數據" 就是指這種還在網絡上傳輸, 未被接收方接收的數據.

例子 SimpleClient.java SimpleServer.java 所示是一對簡單的客戶/服務器程序. SimpleClient 類發送一萬個字符給 SimpleServer, 而後調用Socket close() 方法關閉 Socket.

SimpleServer 經過 ServerSocket accept() 方法接受了 SimpleClient 的鏈接請求後, 並不當即接收客戶發送的數據, 而是睡眠 5 秒鐘後再接收數據. 等到 SimpleServer 開始接收數據時, SimpleClient 有可能已經執行了 Socket close() 方法, 那麼 SimpleServer 還能接收到 SimpleClient 發送的數據嗎?

SimpleClient.java ..., SimpleServer.java ......

SimpleClient.java

System.out.println("開始關閉 Socket");
long begin = System.currentTimeMillis();
socket.close();
long end = System.currentTimeMillis();
System.out.println("關閉Socket 所用的時間爲:" + (end - begin) + "ms");

下面分 3 種狀況演示 SimpleClient 關閉 Socket 的行爲.

未設置 SO_LINGER 選項, SimpleClient 執行 Socket close() 方法時, 當即返回, SimpleClient 的打印結果以下:

開始關閉 Socket
關閉Socket 所用的時間爲:0ms

等到 SimpleClient 結束運行, SimpleServer 可能纔剛剛結束睡眠, 開始接收 SimpleClient 發送的數據. 此時儘管 SimpleClient 已經執行了 Socket close() 方法, 而且 SimpleClient 程序自己也運行結束了, 但從 SimpleServer 的打印結果能夠看出, SimpleServer 仍然接收到了全部的數據. 之因此出現這種狀況, 是由於當 SimpleClient 執行了 Socket close() 方法後, 底層的 Socket 實際上並無真正關閉, SimpleServer 的鏈接依然存在. 底層的 Socket 會存在一段時間, 直到發送完全部的數據.

設置SO_LINGER 選項, socket.setSoLinger(true, 0). 此次當 SimpleClient 執行 Socket close() 方法時, 會強行關閉底層的 Socket, 全部未發送完的數據丟失. SimpleClient 的打印結果以下:

開始關閉 Socket
關閉Socket 所用的時間爲:0ms

從打印結果看出, SimpleClient 執行 Socket close() 方法時, 也當即返回. SimpleServer 結束睡眠, 開始接收 SimpleClient 發送的數據時, 因爲 SimpleClient 已經關閉底層 Socket, 斷開鏈接, 所以 SimpleServer 在讀數據時會拋出 SocketException:

java.net.SocketException: Connection reset

設置SO_LINGER 選項, socket.setSoLinger(true, 3600). 此次當 SimpleClient 執行 Socket close() 方法時, 會進入阻塞狀態, 知道等待了 3600 , 或者底層 Socket 已經把全部未發送的剩餘數據發送完畢, 纔會從 close() 方法返回. SimpleClient 的打印結果以下:

開始關閉 Socket
關閉Socket 所用的時間爲:5648ms

SimpleServer 結束了 5 秒鐘的睡眠, 開始接收 SimpleClient 發送的數據時, SimpleClient 還在這些 Socket close() 方法, 而且處於阻塞狀態. SimpleClient SimpleServer 之間的鏈接依然存在, 所以 SimpleServer 可以接收到 SimpleClient 發送的全部數據.

5.5 SO_RCVBUF 選項

設置該選項: public void setReceiveBufferSize(int size) throws SocketException
讀取該選項: public int getReceiveBufferSize() throws SocketException
SO_RCVBUF 表示 Socket 的用於輸入數據的緩衝區的大小. 通常說來, 傳輸大的連續的數據塊(基於HTTP FTP 協議的通訊) 能夠使用較大的緩衝區, 這能夠減小傳輸數據的次數, 提升傳輸數據的效率. 而對於交互頻繁且單次傳送數據量比較小的通訊方式(Telnet 和 網絡遊戲), 則應該採用小的緩衝區, 確保小批量的數據能及時發送給對方. 這種設定緩衝區大小的原則也一樣適用於 Socket SO_SNDBUF 選項.

若是底層 Socket 不支持 SO_RCVBUF 選項, 那麼 setReceiveBufferSize() 方法會拋出 SocketException.

5.6 SO_SNDBUF 選項

設置該選項: public void setSendBufferSize(int size) throws SocketException
讀取該選項: public int getSendBufferSize() throws SocketException
SO_SNDBUF 表示 Socket 的用於輸出數據的緩衝區的大小. 若是底層 Socket 不支持 SO_SNDBUF 選項, setSendBufferSize() 方法會拋出 SocketException.

5.7 SO_KEEPALIVE 選項

設置該選項: public void setKeepAlive(boolean on) throws SocketException
讀取該選項: public boolean getKeepAlive() throws SocketException //原書中這個方法返回的類型是int
SO_KEEPALIVE 選項爲 true , 表示底層的TCP 實現會監視該鏈接是否有效. 當鏈接處於空閒狀態(鏈接的兩端沒有互相傳送數據) 超過了 2 小時時, 本地的TCP 實現會發送一個數據包給遠程的 Socket. 若是遠程Socket 沒有發回響應, TCP實現就會持續嘗試 11 分鐘, 直到接收到響應爲止. 若是在 12 分鐘內未收到響應, TCP 實現就會自動關閉本地Socket, 斷開鏈接. 在不一樣的網絡平臺上, TCP實現嘗試與遠程Socket 對話的時限有所差異.

SO_KEEPALIVE 選項的默認值爲 false, 表示TCP 不會監視鏈接是否有效, 不活動的客戶端可能會永遠存在下去, 而不會注意到服務器已經崩潰.

如下代碼把 SO_KEEPALIVE 選項設爲 true:

if(!socket.getKeepAlive()) socket.setKeepAlive(true);

5.8 OOBINLINE 選項

設置該選項: public void setOOBInline(boolean on) throws SocketException
讀取該選項: public boolean getOOBInline() throws SocketException //原書中這個方法返回的類型是int
OOBINLINE true , 表示支持發送一個字節的 TCP 緊急數據. Socket 類的 sendUrgentData(int data) 方法用於發送一個字節的 TCP緊急數據.

OOBINLINE 的默認值爲 false, 在這種狀況下, 當接收方收到緊急數據時不做任何處理, 直接將其丟棄. 若是用戶但願發送緊急數據, 應該把 OOBINLINE 設爲 true:

socket.setOOBInline(true);

對於即時類應用或者即時類的遊戲,HTTP協議不少時候沒法知足於咱們的需求。這會,Socket對於咱們來講就很是實用了。下面是本次學習的筆記。主要分異常類型、交互原理、SocketServerSocket、多線程這幾個方面闡述。

 

異常類型

在瞭解Socket的內容以前,先要了解一下涉及到的一些異常類型。如下四種類型都是繼承於IOException,因此不少以後直接彈出IOException便可。

UnkownHostException:   主機名字或IP錯誤

ConnectException:     服務器拒絕鏈接、服務器沒有啓動、(超出隊列數,拒絕鏈接)

SocketTimeoutException: 鏈接超時

BindException: Socket對象沒法與制定的本地IP地址或端口綁定

 

交互過程

 

SocketServerSocket的交互,下面的圖片我以爲已經說的很詳細很清楚了。

 

 

Socket

構造函數

Socket()

Socket(InetAddress address, int port)throws UnknownHostException, IOException

Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException

Socket(String host, int port)throwsUnknownHostException,IOException

Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException

 

除去第一種不帶參數的以外,其它構造函數會嘗試創建與服務器的鏈接。若是失敗會拋出IOException錯誤。若是成功,則返回Socket對象。

InetAddress是一個用於記錄主機的類,其靜態getHostByName(String msg)能夠返回一個實例,其靜態方法getLocalHost()也能夠得到當前主機的IP地址,並返回一個實例。Socket(String host, int port, InetAddress localAddress, int localPort)構造函數的參數分別爲目標IP、目標端口、綁定本地IP、綁定本地端口。

 

Socket方法

getInetAddress();   遠程服務端的IP地址

getPort();       遠程服務端的端口

getLocalAddress()   本地客戶端的IP地址

getLocalPort()     本地客戶端的端口

getInputStream();  得到輸入流

getOutStream();   得到輸出流

值得注意的是,在這些方法裏面,最重要的就是getInputStream()getOutputStream()了。

 

Socket狀態

isClosed(); //鏈接是否已關閉,若關閉,返回true;不然返回false

isConnect();//若是曾經鏈接過,返回true;不然返回false

isBound(); //若是Socket已經與本地一個端口綁定,返回true;不然返回false

若是要確認Socket的狀態是否處於鏈接中,下面語句是很好的判斷方式。

booleanisConnection=socket.isConnected() && !socket.isClosed(); //判斷當前是否處於鏈接

 

半關閉Socket

不少時候,咱們並不知道在得到的輸入流裏面到底讀多長才結束。下面是一些比較廣泛的方法:

·自定義標識符(譬以下面的例子,當受到「bye」字符串的時候,關閉Socket)

·告知讀取長度(有些自定義協議的,固定前幾個字節表示讀取的長度的)

·讀完全部數據

·當Socket調用close的時候關閉的時候,關閉其輸入輸出流

 

ServerSocket

構造函數

ServerSocket()throws IOException

ServerSocket(int port)throws IOException

ServerSocket(int port, int backlog)throws IOException

ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException

 

注意點:

1. port服務端要監聽的端口;backlog客戶端鏈接請求的隊列長度;bindAddr服務端綁定IP

2. 若是端口被佔用或者沒有權限使用某些端口會拋出BindException錯誤。譬如1~1023的端口須要管理員才擁有權限綁定。

3. 若是設置端口爲0,則系統會自動爲其分配一個端口;

4.bindAddr用於綁定服務器IP,爲何會有這樣的設置呢,譬若有些機器有多個網卡。

5. ServerSocket一旦綁定了監聽端口,就沒法更改。ServerSocket()能夠實如今綁定端口前設置其餘的參數。

 

單線程的ServerSocket例子

 

publicvoidservice(){

while(true){

Socket socket=null;

try{

socket=serverSocket.accept();//從鏈接隊列中取出一個鏈接,若是沒有則等待

System.out.println("新增鏈接:"+socket.getInetAddress()+":"+socket.getPort());

...//接收和發送數據

}catch(IOException e){e.printStackTrace();}finally{

try{

if(socket!=null) socket.close();//與一個客戶端通訊結束後,要關閉Socket

}catch(IOException e){e.printStackTrace();}

}

}

}

 

 

多線程的ServerSocket

多線程的好處不用多說,並且大多數的場景都是多線程的,不管是咱們的即時類遊戲仍是IM,多線程的需求都是必須的。下面說說實現方式:

·主線程會循環執行ServerSocket.accept()

·當拿到客戶端鏈接請求的時候,就會將Socket對象傳遞給多線程,讓多線程去執行具體的操做;

實現多線程的方法要麼繼承Thread類,要麼實現Runnable接口。固然也能夠使用線程池,但實現的本質都是差很少的。

 

這裏舉例:

下面代碼爲服務器的主線程。爲每一個客戶分配一個工做線程:

 

publicvoidservice(){

while(true){

Socket socket=null;

try{

socket=serverSocket.accept(); //主線程獲取客戶端鏈接

Thread workThread=newThread(newHandler(socket)); //建立線程

workThread.start(); //啓動線程

}catch(Exception e){

e.printStackTrace();

}

}

}

 

 

固然這裏的重點在於如何實現Handler這個類。Handler須要實現Runnable接口:

 

classHandler implementsRunnable{

privateSocket socket;

publicHandler(Socket socket){

this.socket=socket;

}

 

publicvoidrun(){

try{

System.out.println("新鏈接:"+socket.getInetAddress()+":"+socket.getPort());

Thread.sleep(10000);

}catch(Exception e){e.printStackTrace();}finally{

try{

System.out.println("關閉鏈接:"+socket.getInetAddress()+":"+socket.getPort());

if(socket!=null)socket.close();

}catch(IOException e){

e.printStackTrace();

}

}

}

}

 

固然是先多線程還有其它的方式,譬如線程池,或者JVM自帶的線程池均可以。這裏就不說明了。

Java讀取、建立xml(經過dom方式)

建立一個接口

XmlInterface.java

publicinterfaceXmlInterface {

 

/**

* 創建XML文檔

* @param fileName 文件全路徑名稱

*/

publicvoidcreateXml(String fileName);

/**

* 解析XML文檔

* @param fileName 文件全路徑名稱

*/

publicvoidparserXml(String fileName);

}

接口實現

XmlImpl.java

packagecom.test.xml;

 

importjava.io.FileNotFoundException;

importjava.io.FileOutputStream;

importjava.io.IOException;

importjava.io.PrintWriter;

importjavax.xml.parsers.DocumentBuilder;

importjavax.xml.parsers.DocumentBuilderFactory;

importjavax.xml.parsers.ParserConfigurationException;

importjavax.xml.transform.OutputKeys;

importjavax.xml.transform.Transformer;

importjavax.xml.transform.TransformerConfigurationException;

importjavax.xml.transform.TransformerException;

importjavax.xml.transform.TransformerFactory;

importjavax.xml.transform.dom.DOMSource;

importjavax.xml.transform.stream.StreamResult;

importorg.w3c.dom.Document;

importorg.w3c.dom.Element;

importorg.w3c.dom.Node;

importorg.w3c.dom.NodeList;

importorg.xml.sax.SAXException;

 

publicclassXmlImpl implementsXmlInterface{

privateDocument document;

 

publicvoidinit() {

try{

DocumentBuilderFactory factory = DocumentBuilderFactory

.newInstance();

DocumentBuilder builder = factory.newDocumentBuilder();

this.document = builder.newDocument();

} catch(ParserConfigurationException e) {

System.out.println(e.getMessage());

}

}

 

publicvoidcreateXml(String fileName) {

Element root = this.document.createElement("scores");

this.document.appendChild(root);

Element employee = this.document.createElement("employee");

Element name = this.document.createElement("name");

name.appendChild(this.document.createTextNode("wangchenyang"));

employee.appendChild(name);

Element sex = this.document.createElement("sex");

sex.appendChild(this.document.createTextNode("m"));

employee.appendChild(sex);

Element age = this.document.createElement("age");

age.appendChild(this.document.createTextNode("26"));

employee.appendChild(age);

root.appendChild(employee);

TransformerFactory tf = TransformerFactory.newInstance();

try{

Transformer transformer = tf.newTransformer();

DOMSource source = newDOMSource(document);

transformer.setOutputProperty(OutputKeys.ENCODING, "gb2312");

transformer.setOutputProperty(OutputKeys.INDENT, "yes");

PrintWriter pw = newPrintWriter(newFileOutputStream(fileName));

StreamResult result = newStreamResult(pw);

transformer.transform(source, result);

System.out.println("生成XML文件成功!");

} catch(TransformerConfigurationException e) {

System.out.println(e.getMessage());

} catch(IllegalArgumentException e) {

System.out.println(e.getMessage());

} catch(FileNotFoundException e) {

System.out.println(e.getMessage());

} catch(TransformerException e) {

System.out.println(e.getMessage());

}

}

 

publicvoidparserXml(String fileName) {

try{

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

DocumentBuilder db = dbf.newDocumentBuilder();

Document document = db.parse(fileName);

 

NodeList employees = document.getChildNodes();

for(inti = 0; i < employees.getLength(); i++) {

Node employee = employees.item(i);

NodeList employeeInfo = employee.getChildNodes();

for(intj = 0; j < employeeInfo.getLength(); j++) {

Node node = employeeInfo.item(j);

NodeList employeeMeta = node.getChildNodes();

for(intk = 0; k < employeeMeta.getLength(); k++) {

System.out.println(employeeMeta.item(k).getNodeName()

+ ":"+ employeeMeta.item(k).getTextContent());

}

}

}

System.out.println("解析完畢");

} catch(FileNotFoundException e) {

System.out.println(e.getMessage());

} catch(ParserConfigurationException e) {

System.out.println(e.getMessage());

} catch(SAXException e) {

System.out.println(e.getMessage());

} catch(IOException e) {

System.out.println(e.getMessage());

}

}

}

測試

publicclassMain {

 

publicstaticvoidmain(String args[]){

XmlImpl dd=newXmlImpl();

String str="D:/grade.xml";

dd.init();

dd.createXml(str); //建立xml

dd.parserXml(str); //讀取xml

}

}

結果

生成xml

 

<?xml version="1.0" encoding="GB2312"?>
<scores>
<employee>
<name>wangchenyang</name>
<sex>m</sex>
<age>26</age>
</employee>
</scores>

 

讀取xml

生成XML文件成功!

#text:

 

name:wangchenyang

#text:

 

sex:m

#text:

 

age:26

#text:

 

解析完畢

 

 

javaweb學習總結()——Servlet開發()

1、Servlet簡介

  Servletsun公司提供的一門用於開發動態web資源的技術。
  Sun公司在其API中提供了一個servlet接口,用戶若想用發一個動態web資源(即開發一個Java程序向瀏覽器輸出數據),須要完成如下2個步驟:
  1、編寫一個Java類,實現servlet接口。
  2、把開發好的Java類部署到web服務器中。
  按照一種約定俗成的稱呼習慣,一般咱們也把實現了servlet接口的java程序,稱之爲Servlet

2、Servlet的運行過程

Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet訪問請求後:
  ①Web服務器首先檢查是否已經裝載並建立了該Servlet的實例對象。若是是,則直接執行第步,不然,執行第步。
  裝載並建立該Servlet的一個實例對象。
  調用Servlet實例對象的init()方法。
  建立一個用於封裝HTTP請求消息的HttpServletRequest對象和一個表明HTTP響應消息的HttpServletResponse對象,而後調用Servletservice()方法並將請求和響應對象做爲參數傳遞進去。
  ⑤WEB應用程序被中止或從新啓動以前,Servlet引擎將卸載Servlet,並在卸載以前調用Servletdestroy()方法。

 

 

 

 

 

 

 

3、Servlet調用圖

 

4、在Eclipse中開發Servlet

  在eclipse中新建一個web project工程,eclipse會自動建立下圖所示目錄結構:

 

4.一、Servlet接口實現類

  Servlet接口SUN公司定義了兩個默認實現類,分別爲:GenericServletHttpServlet

  HttpServlet指可以處理HTTP請求的servlet,它在原有Servlet接口上添加了一些與HTTP協議處理方法,它比Servlet接口的功能更爲強大。所以開發人員在編寫Servlet時,一般應繼承這個類,而避免直接去實現Servlet接口。
  HttpServlet在實現Servlet接口時,覆寫了service方法,該方法體內的代碼會自動判斷用戶的請求方式,如爲GET請求,則調用HttpServletdoGet方法,如爲Post請求,則調用doPost方法。所以,開發人員在編寫Servlet時,一般只須要覆寫doGetdoPost方法,而不要去覆寫service方法。

4.二、經過Eclipse建立和編寫Servlet

  選中gacl.servlet.study包,右鍵→New→Servlet,以下圖所示:

  

  

  

  這樣,咱們就經過Eclipse幫咱們建立好一個名字爲ServletDemo1Servlet,建立好的ServletDemo01裏面會有以下代碼:

 

1packagegacl.servlet.study;23importjava.io.IOException;4importjava.io.PrintWriter;56importjavax.servlet.ServletException;7importjavax.servlet.http.HttpServlet;8importjavax.servlet.http.HttpServletRequest;9importjavax.servlet.http.HttpServletResponse;1011publicclassServletDemo1 extendsHttpServlet {1213/**14* The doGet method of the servlet. <br>15*16* This method is called when a form has its tag value method equals to get.17* 18* @paramrequest the request send by the client to the server19* @paramresponse the response send by the server to the client20* @throwsServletException if an error occurred21* @throwsIOException if an error occurred22*/23publicvoiddoGet(HttpServletRequest request, HttpServletResponse response)24throwsServletException, IOException {2526response.setContentType("text/html");27PrintWriter out = response.getWriter();28out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");29out.println("<HTML>");30out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");31out.println(" <BODY>");32out.print(" This is ");33out.print(this.getClass());34out.println(", using the GET method");35out.println(" </BODY>");36out.println("</HTML>");37out.flush();38out.close();39}4041/**42* The doPost method of the servlet. <br>43*44* This method is called when a form has its tag value method equals to post.45* 46* @paramrequest the request send by the client to the server47* @paramresponse the response send by the server to the client48* @throwsServletException if an error occurred49* @throwsIOException if an error occurred50*/51publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)52throwsServletException, IOException {5354response.setContentType("text/html");55PrintWriter out = response.getWriter();56out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");57out.println("<HTML>");58out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");59out.println(" <BODY>");60out.print(" This is ");61out.print(this.getClass());62out.println(", using the POST method");63out.println(" </BODY>");64out.println("</HTML>");65out.flush();66out.close();67}6869}

 

  這些代碼都是Eclipse自動生成的,而web.xml文件中也多了<servlet></servlet><servlet-mapping></servlet-mapping>兩對標籤,這兩對標籤是配置ServletDemo1的,以下圖所示:

 

而後咱們就能夠經過瀏覽器訪問ServletDemo1這個Servlet,以下圖所示:

  

5、Servlet開發注意細節

5.一、Servlet訪問URL映射配置

  因爲客戶端是經過URL地址訪問web服務器中的資源,因此Servlet程序若想被外界訪問,必須把servlet程序映射到一個URL地址上,這個工做在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
  <servlet>元素用於註冊Servlet,它包含有兩個主要的子元素:<servlet-name><servlet-class>,分別用於設置Servlet的註冊名稱和Servlet的完整類名。
一個<servlet-mapping>元素用於映射一個已註冊的Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name><url-pattern>,分別用於指定Servlet的註冊名稱和Servlet的對外訪問路徑。例如:

 

1<servlet>2<servlet-name>ServletDemo1</servlet-name>3<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>4</servlet>56<servlet-mapping>7<servlet-name>ServletDemo1</servlet-name>8<url-pattern>/servlet/ServletDemo1</url-pattern>9</servlet-mapping>

 

  同一個Servlet能夠被映射到多個URL上,即多個<servlet-mapping>元素的<servlet-name>子元素的設置值能夠是同一個Servlet的註冊名。 例如:

 

1<servlet>2<servlet-name>ServletDemo1</servlet-name>3<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>4</servlet>56<servlet-mapping>7<servlet-name>ServletDemo1</servlet-name>8<url-pattern>/servlet/ServletDemo1</url-pattern>9</servlet-mapping>10<servlet-mapping>11<servlet-name>ServletDemo1</servlet-name>12<url-pattern>/1.htm</url-pattern>13</servlet-mapping>14<servlet-mapping>15<servlet-name>ServletDemo1</servlet-name>16<url-pattern>/2.jsp</url-pattern>17</servlet-mapping>18<servlet-mapping>19<servlet-name>ServletDemo1</servlet-name>20<url-pattern>/3.php</url-pattern>21</servlet-mapping>22<servlet-mapping>23<servlet-name>ServletDemo1</servlet-name>24<url-pattern>/4.ASPX</url-pattern>25</servlet-mapping>

 

  經過上面的配置,當咱們想訪問名稱是ServletDemo1Servlet,能夠使用以下的幾個地址去訪問:

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/servlet/ServletDemo1

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/1.htm

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/2.jsp

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/3.php

  http://localhost:8080/JavaWeb_Servlet_Study_20140531/4.ASPX

  ServletDemo1被映射到了多個URL上。

5.二、Servlet訪問URL使用*通配符映射  

Servlet映射到的URL中也能夠使用*通配符,可是隻能有兩種固定的格式:一種格式是"*.擴展名",另外一種格式是以正斜槓(/)開頭並以"/*"結尾。例如:

  

 

1<servlet>2<servlet-name>ServletDemo1</servlet-name>3<servlet-class>gacl.servlet.study.ServletDemo1</servlet-class>4</servlet>56<servlet-mapping>7<servlet-name>ServletDemo1</servlet-name>8<url-pattern>/*</url-pattern>

 

  *能夠匹配任意的字符,因此此時能夠用任意的URL去訪問ServletDemo1這個Servlet,以下圖所示:

  

對於以下的一些映射關係:
  Servlet1 映射到 /abc/*
  Servlet2 映射到 /*
  Servlet3 映射到 /abc
  Servlet4 映射到 *.do
問題:
  當請求URL「/abc/a.html」「/abc/*」「/*」都匹配,哪一個servlet響應
Servlet引擎將調用Servlet1
  當請求URL「/abc」時,「/abc/*」「/abc」都匹配,哪一個servlet響應
Servlet引擎將調用Servlet3
  當請求URL「/abc/a.do」時,「/abc/*」「*.do」都匹配,哪一個servlet響應
Servlet引擎將調用Servlet1
  當請求URL「/a.do」時,「/*」「*.do」都匹配,哪一個servlet響應
Servlet引擎將調用Servlet2
  當請求URL「/xxx/yyy/a.do」時,「/*」「*.do」都匹配,哪一個servlet響應
Servlet引擎將調用Servlet2
  匹配的原則就是"誰長得更像就找誰"

5.三、Servlet與普通Java類的區別  

  Servlet是一個供其餘Java程序(Servlet引擎)調用的Java類,它不能獨立運行,它的運行徹底由Servlet引擎來控制和調度。
  針對客戶端的屢次Servlet請求,一般狀況下,服務器只會建立一個Servlet實例對象,也就是說Servlet實例對象一旦建立,它就會駐留在內存中,爲後續的其它請求服務,直至web容器退出,servlet實例對象纔會銷燬。
  在Servlet的整個生命週期內,Servletinit方法只被調用一次。而對一個Servlet的每次訪問請求都致使Servlet引擎調用一次servletservice方法。對於每次訪問請求,Servlet引擎都會建立一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,而後將這兩個對象做爲參數傳遞給它調用的Servletservice()方法,service方法再根據請求方式分別調用doXXX方法。

  若是在<servlet>元素中配置了一個<load-on-startup>元素,那麼WEB應用程序在啓動時,就會裝載並建立Servlet的實例對象、以及調用Servlet實例對象的init()方法。
舉例:
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

  用途:爲web應用寫一個InitServlet,這個servlet配置爲啓動時裝載,爲整個web應用建立必要的數據庫表和數據。

5.四、缺省Servlet

  若是某個Servlet的映射路徑僅僅爲一個正斜槓(/),那麼這個Servlet就成爲當前Web應用程序的缺省Servlet
  凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給缺省Servlet處理,也就是說,缺省Servlet用於處理全部其餘Servlet都不處理的訪問請求。 例如:

 

1<servlet>2<servlet-name>ServletDemo2</servlet-name>3<servlet-class>gacl.servlet.study.ServletDemo2</servlet-class>4<load-on-startup>1</load-on-startup>5</servlet>67<!-- ServletDemo2配置成缺省Servlet -->8<servlet-mapping>9<servlet-name>ServletDemo2</servlet-name>10<url-pattern>/</url-pattern>11</servlet-mapping>

 

  當訪問不存在的Servlet時,就使用配置的默認Servlet進行處理,以下圖所示:

  

  在<tomcat的安裝目錄>\conf\web.xml文件中,註冊了一個名稱爲org.apache.catalina.servlets.DefaultServletServlet,並將這個Servlet設置爲了缺省Servlet

 

1<servlet>2<servlet-name>default</servlet-name>3<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>4<init-param>5<param-name>debug</param-name>6<param-value>0</param-value>7</init-param>8<init-param>9<param-name>listings</param-name>10<param-value>false</param-value>11</init-param>12<load-on-startup>1</load-on-startup>13</servlet>1415<!-- The mapping for the default servlet -->16<servlet-mapping>17<servlet-name>default</servlet-name>18<url-pattern>/</url-pattern>19</servlet-mapping>

 

  當訪問Tomcat服務器中的某個靜態HTML文件和圖片時,其實是在訪問這個缺省Servlet

5.五、Servlet的線程安全問題

  當多個客戶端併發訪問同一個Servlet時,web服務器會爲每個客戶端的訪問請求建立一個線程,並在這個線程上調用Servletservice方法,所以service方法內若是訪問了同一個資源的話,就有可能引起線程安全問題。例以下面的代碼:

不存在線程安全問題的代碼:

 

1packagegacl.servlet.study;23importjava.io.IOException;45importjavax.servlet.ServletException;6importjavax.servlet.http.HttpServlet;7importjavax.servlet.http.HttpServletRequest;8importjavax.servlet.http.HttpServletResponse;910publicclassServletDemo3 extendsHttpServlet {111213publicvoiddoGet(HttpServletRequest request, HttpServletResponse response)14throwsServletException, IOException {1516/**17* 當多線程併發訪問這個方法裏面的代碼時,會存在線程安全問題嗎18* i變量被多個線程併發訪問,可是沒有線程安全問題,由於idoGet方法裏面的局部變量,19* 當有多個線程併發訪問doGet方法時,每個線程裏面都有本身的i變量,20* 各個線程操做的都是本身的i變量,因此不存在線程安全問題21* 多線程併發訪問某一個方法的時候,若是在方法內部定義了一些資源(變量,集合等)22* 那麼每個線程都有這些東西,因此就不存在線程安全問題了23*/24inti=1;25i++;26response.getWriter().write(i);27}2829publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)30throwsServletException, IOException {31doGet(request, response);32}3334}

 

存在線程安全問題的代碼:

 

1packagegacl.servlet.study;23importjava.io.IOException;45importjavax.servlet.ServletException;6importjavax.servlet.http.HttpServlet;7importjavax.servlet.http.HttpServletRequest;8importjavax.servlet.http.HttpServletResponse;910publicclassServletDemo3 extendsHttpServlet {1112inti=1;13publicvoiddoGet(HttpServletRequest request, HttpServletResponse response)14throwsServletException, IOException {1516i++;17try{18Thread.sleep(1000*4);19} catch(InterruptedException e) {20e.printStackTrace();21}22response.getWriter().write(i+"");23}2425publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)26throwsServletException, IOException {27doGet(request, response);28}2930}

 

  把i定義成全局變量,當多個線程併發訪問變量i時,就會存在線程安全問題了,以下圖所示:同時開啓兩個瀏覽器模擬併發訪問同一個Servlet,原本正常來講,第一個瀏覽器應該看到2,而第二個瀏覽器應該看到3的,結果兩個瀏覽器都看到了3,這就不正常。

  

  線程安全問題只存在多個線程併發操做同一個資源的狀況下,因此在編寫Servlet的時候,若是併發訪問某一個資源(變量,集合等),就會存在線程安全問題,那麼該如何解決這個問題呢?

先看看下面的代碼:

 

1packagegacl.servlet.study;23importjava.io.IOException;45importjavax.servlet.ServletException;6importjavax.servlet.http.HttpServlet;7importjavax.servlet.http.HttpServletRequest;8importjavax.servlet.http.HttpServletResponse;91011publicclassServletDemo3 extendsHttpServlet {1213inti=1;14publicvoiddoGet(HttpServletRequest request, HttpServletResponse response)15throwsServletException, IOException {16/**17* 加了synchronized後,併發訪問i時就不存在線程安全問題了,18* 爲何加了synchronized後就沒有線程安全問題了呢?19* 假如如今有一個線程訪問Servlet對象,那麼它就先拿到了Servlet對象的那把鎖20* 等到它執行完以後纔會把鎖還給Servlet對象,因爲是它先拿到了Servlet對象的那把鎖,21* 因此當有別的線程來訪問這個Servlet對象時,因爲鎖已經被以前的線程拿走了,後面的線程只能排隊等候了22* 23*/24synchronized(this) {//java中,每個對象都有一把鎖,這裏的this指的就是Servlet對象25i++;26try{27Thread.sleep(1000*4);28} catch(InterruptedException e) {29e.printStackTrace();30}31response.getWriter().write(i+"");32}3334}3536publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)37throwsServletException, IOException {38doGet(request, response);39}4041}

 

  如今這種作法是給Servlet對象加了一把鎖,保證任什麼時候候都只有一個線程在訪問該Servlet對象裏面的資源,這樣就不存在線程安全問題了,以下圖所示:

  

  這種作法雖然解決了線程安全問題,可是編寫Servlet卻萬萬不能用這種方式處理線程安全問題,假若有9999我的同時訪問這個Servlet,那麼這9999我的必須按前後順序排隊輪流訪問。

  針對Servlet的線程安全問題,Sun公司是提供有解決方案的:Servlet去實現一個SingleThreadModel接口,若是某個Servlet實現了SingleThreadModel接口,那麼Servlet引擎將以單線程模式來調用其service方法。
  查看SevletAPI能夠看到,SingleThreadModel接口中沒有定義任何方法和常量,Java中,把沒有定義任何方法和常量的接口稱之爲標記接口,常常看到的一個最典型的標記接口就是"Serializable",這個接口也是沒有定義任何方法和常量的,標記接口在Java中有什麼用呢?主要做用就是給某個對象打上一個標誌,告訴JVM,這個對象能夠作什麼,好比實現了"Serializable"接口的類的對象就能夠被序列化,還有一個"Cloneable"接口,這個也是一個標記接口,在默認狀況下,Java中的對象是不容許被克隆的,就像現實生活中的人同樣,不容許克隆,可是隻要實現了"Cloneable"接口,那麼對象就能夠被克隆了。

  讓Servlet實現了SingleThreadModel接口,只要在Servlet類的定義中增長實現SingleThreadModel接口的聲明便可。
  對於實現了SingleThreadModel接口的ServletServlet引擎仍然支持對該Servlet的多線程併發訪問,其採用的方式是產生多個Servlet實例對象,併發的每一個線程分別調用一個獨立的Servlet實例對象
  實現SingleThreadModel接口並不能真正解決Servlet的線程安全問題,由於Servlet引擎會建立多個Servlet實例對象,而真正意義上解決多線程安全問題是指一個Servlet實例對象被多個線程同時調用的問題。事實上,在Servlet API 2.4中,已經將SingleThreadModel標記爲Deprecated(過期的)。

 

Servlet生命週期與工做原理

Servlet生命週期分爲三個階段:

  1,初始化階段調用init()方法

  2,響應客戶請求階段  調用service()方法

  3,終止階段  調用destroy()方法

Servlet初始化階段:

  在下列時刻Servlet容器裝載Servlet

    1Servlet容器啓動時自動裝載某些Servlet,實現它只須要在web.XML文件中的<Servlet></Servlet>之間添加以下代碼:

<loadon-startup>1</loadon-startup>

    2,在Servlet容器啓動後,客戶首次向Servlet發送請求

    3Servlet類文件被更新後,從新裝載Servlet

  Servlet被裝載後,Servlet容器建立一個Servlet實例而且調用Servletinit()方法進行初始化。在Servlet的整個生命週期內,init()方法只被調用一次。

    

Servlet工做原理:

  首先簡單解釋一下Servlet接收和響應客戶請求的過程,首先客戶發送一個請求,Servlet是調用service()方法對請求進行響應的,經過源代碼可見,service()方法中對請求的方式進行了匹配,選擇調用doGet,doPost等這些方法,而後再進入對應的方法中調用邏輯層的方法,實現對客戶的響應。在Servlet接口和GenericServlet中是沒有doGet,doPost等等這些方法的,HttpServlet中定義了這些方法,可是都是返回error信息,因此,咱們每次定義一個Servlet的時候,都必須實現doGetdoPost等這些方法。

  每個自定義的Servlet都必須實現Servlet的接口,Servlet接口中定義了五個方法,其中比較重要的三個方法涉及到Servlet的生命週期,分別是上文提到的init(),service(),destroy()方法。GenericServlet是一個通用的,不特定於任何協議的Servlet,它實現了Servlet接口。而HttpServlet繼承於GenericServlet,所以HttpServlet也實現了Servlet接口。因此咱們定義Servlet的時候只須要繼承HttpServlet便可。

  Servlet接口和GenericServlet是不特定於任何協議的,而HttpServlet是特定於HTTP協議的類,因此HttpServlet中實現了service()方法,並將請求ServletRequest,ServletResponse強轉爲HttpRequestHttpResponse

publicvoidservice(ServletRequest req,ServletResponse res)

throwsServletException,IOException

{

HttpRequest request;

HttpResponse response;

 

try

{

req = (HttpRequest)request;

res = (HttpResponse)response;

}catch(ClassCastException e)

{

thrownewServletException("non-HTTP request response");

}

service(request,response);

}

代碼的最後調用了HTTPServlet本身的service(request,response)方法,而後根據請求去調用對應的doXXX方法,由於HttpServlet中的doXXX方法都是返回錯誤信息,

protectedvoiddoGet(HttpServletRequest res,HttpServletResponse resp)

throwsServletException,IOException

{

String protocol = req.getProtocol();

String msg = IStrings.getString("http.method_get_not_supported");

if(protocol.equals("1.1"))

{

resp.sendError(HttpServletResponse.SC.METHOD.NOT.ALLOWED,msg);

}

esle

{

resp.sendError(HttpServletResponse.SC_BAD_REQUEST,msg);

}

}

因此須要咱們在自定義的Servletoverride這些方法!

    源碼面前,了無祕密!

---------------------------------------------------------------------------------------------------------------------------------

Servlet響應請求階段:

  對於用戶到達Servlet的請求,Servlet容器會建立特定於這個請求的ServletRequest對象和ServletResponse對象,而後調用Servletservice方法。service方法從ServletRequest對象得到客戶請求信息,處理該請求,並經過ServletResponse對象向客戶返回響應信息。

  對於Tomcat來講,它會將傳遞過來的參數放在一個Hashtable中,該Hashtable的定義是:

privateHashtable<String String[]> paramHashStringArray = newHashtable<String String[]>();

  這是一個String-->String[]的鍵值映射。

  HashMap線程不安全的,Hashtable線程安全。

-----------------------------------------------------------------------------------------------------------------------------------

Servlet終止階段:

  當WEB應用被終止,或Servlet容器終止運行,或Servlet容器從新裝載Servlet新實例時,Servlet容器會先調用Servletdestroy()方法,在destroy()方法中能夠釋放掉Servlet所佔用的資源。

-----------------------------------------------------------------------------------------------------------------------------------

Servlet什麼時候被建立:

  1,默認狀況下,當WEB客戶第一次請求訪問某個Servlet的時候,WEB容器將建立這個Servlet的實例。

  2,當web.xml文件中若是<servlet>元素中指定了<load-on-startup>子元素時,Servlet容器在啓動web服務器時,將按照順序建立並初始化Servlet對象。

  注意:在web.xml文件中,某些Servlet只有<serlvet>元素,沒有<servlet-mapping>元素,這樣咱們沒法經過url的方式訪問這些Servlet,這種Servlet一般會在<servlet>元素中配置一個<load-on-startup>子元素,讓容器在啓動的時候自動加載這些Servlet並調用init()方法,完成一些全局性的初始化工做。

 

Web應用什麼時候被啓動:

  1,當Servlet容器啓動的時候,全部的Web應用都會被啓動

  2,控制器啓動web應用

-----------------------------------------------------------------------------------------------------------------------------------------------

ServletJSP的比較:

  有許多類似之處,均可以生成動態網頁。

  JSP的優勢是擅長於網頁製做,生成動態頁面比較直觀,缺點是不容易跟蹤與排錯。

  Servlet是純Java語言,擅長於處理流程和業務邏輯,缺點是生成動態網頁不直觀。

Servlet的生命週期

Servlet是運行在Servlet容器(有時候也叫引擎,是基於服務器和應用程序服務器的一部分,用於在發送的請求和響應之上提供網絡服務,解碼基於MIME的請求,格式化基於MIME的響應。經常使用的TomcatJbossweblogic都是servlet容器)中的,其生命週期由容器來管理。Servlet的生命週期經過java.servlet.Servlet接口中的init(),service()destory()

方法表示。Servlet的生命週期有四個階段:加載並實例化,初始化,請求處理,銷燬。

1.加載並實例化

Servlet容器負責加載和實例化Servlet。當Servlet容器啓動時,或者在容器檢測到須要這個Servlet來響應第一個請求時,建立Servlet實例。當Servlet容器啓動後,Servlet經過類加載器來加載Servlet類,加載完成後再new一個Servlet對象來完成實例化。類文件被更新後,也會從新裝載Servlet

2.初始化

Servlet實例化以後,容器將調用init()方法,並傳遞實現ServletConfig接口的對象。在init()方法中,Servlet能夠從web.xml中讀取配置參數,或者執行任何其餘一次性活動,在Servlet的整個生命週期,init()方法只被調用一次。

3.請求處理

Servlet初始化以後,容器就能夠準備處理客戶端的請求了。當容器收到對這一Servlet的請求,就調用Servletservice()方法,並把請求和響應對象,做爲參數傳遞。當並行的請求到來時,多個service()方法可以同時運行在 獨立的線程中。經過分析ServletRequest對象或者HttpServletRequest對象,service()方法處理用戶的請求,並調用ServletResponse或者HttpServletResponse對象來響應。

4.銷燬

一旦Servlet容器檢測到一個Servlet要卸載,這多是由於要回收資源或者由於它正在被關閉,容易會在全部的Servletservice()線程以後,調用destory()方法。而後,Servlet就能夠進行無用存儲單元收集清理。這樣Servlet就被銷燬了。這四個階段共同決定了Servlet的生命週期。

 

JSP的生命週期

JSP頁面必須轉換成Servlet,才能對請求進行服務,所以JSP的底層徹底是Servlet。這樣看來JSP 的生命週期就包括六個階段:轉換,編譯,加載並實例化,初始化(_jspInit),請求處理(_jspService()調用),銷燬(_jspDestory())。

 

轉換:就是web容器將JSP文件轉換成一個包含了Servlet類定義的java源文件。

編譯:把在轉換階段建立的java源文件變異成類文件。

JSP 生命週期其餘的四個階段跟Servlet生命週期相同。

Java中的ELJSTl

一.EL

EL語法

1.「${」做爲開始,以「}」做爲結束

直接使用變量名獲取值$

${ username }

2.變量屬性範圍名稱

 

page

pageScope,例如${pageScope.username},表示在page範圍內查找username變量,找不到返回Null

request

requstScope

session

sessionScope

application

applicationScope

 

3.EL隱式對象

 

做用域訪問對象 1.PageScope 2.RequestScope 3.SessionScope 4. ApplicationScope

參數訪問對象 1.param 2.paramValues

jsp隱式對象 1.pagecontext

 

RequestScope的使用:

String name=(String)session.setAttribute("user",user);

在會員登入後將user對象保存到session

.jsp使用EL獲取是就能夠使用

歡迎您:${sessionScope.user.name}

其它做用域對象使用相似

Param的使用:

在一個登錄提交頁面提交後,接受的頁面能夠這樣接受參數

用戶名:${param.uid}

密碼:${param.pass}

Param獲取的是單個的參數

Paramvalues的使用:

興趣愛好:${paramValues.ch[0]},${paramValues.ch[1]},${paramValues.ch[2]}

Paramvalues獲取的是一個集合

Pagecontext的使用:

請求的IP:${pageContext.request.remoteAddr}

Pagecontext能夠獲取到請求裏包含的信息

二.JSTL

EL中不存在邏輯處理,JSTL實現JSP頁面中邏輯處理,全部二者須要聯合使用

JSTL的使用前須要在網頁頂部加入下面內容

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

(可能導入了這句話仍是使用不了JSTL,這可能和版本有關,解決辦法是在WEB-INFO目錄下的lib導入兩個jar包,網上能夠下,我這裏也有個人百度網盤的下載地址連接http://pan.baidu.com/share/link?shareid=64504&uk=909074031

 

通用標籤 set out remove

條件標籤庫 if

迭代標籤庫 foreach

 

通用標籤其實通常都用的不多,主要使用的仍是條件標籤迭代標籤

通用標籤

Set的使用

<c:set var= "example" value="${100+1}" scope="session" />

Out的使用

<c:out value="${example}"/>

Remove的使用

<c:remove var= "example" scope="session"/>

 

條件標籤

<c:if test="codition" var="name" scope="applicationArea" >

條件符合時執行的代碼

</c:if>

Condition是判斷的條件

Name是判斷條件的結果是truefalse

Scope是做用域的範圍

以下:

<c:iftest="${user==null}"var="isLogin"></c:if>

判斷登錄時是否獲取到了user對象的結果,若是爲null的話islogin的值爲true

迭代標籤

<c:forEach items=collection

var="varName" varStatus="vatStatusName" begin="start" end="end" step="count">

循環體代碼

</forEach>

Items是要遍歷的集合對象

Var集合內數據的名稱

Varstatus指定var成員的信息

案例:

<c:forEachitems="${requestScope.pagelist}"var="news"varStatus="status">

<li>${news.title}<span>時間:${news.publictime}<ahref='ManageNewsServlet?type=update&id=${news.newsid}'>修改</a>

</span></li>

每遍歷5條數據後就間隔一個<li class='space'></li>

Status.index是當前變量的索引值

<c:iftest="$(status.index%5==0 && status.index>0)">

<liclass='space'></li>

</c:if>

</c:forEach>

JavaWeb過濾器.監聽器.攔截器-原理&區別-我的總結(轉)

一、攔截器是基於java的反射機制的,而過濾器是基於函數回調

二、過濾器依賴與servlet容器,而攔截器不依賴與servlet容器

三、攔截器只能對action請求起做用,而過濾器則能夠對幾乎全部的請求起做用

四、攔截器能夠訪問action上下文、值棧裏的對象,而過濾器不能

五、在action的生命週期中,攔截器能夠屢次被調用,而過濾器只能在容器初始化時被調用一次

 

攔截器 :是在面向切面編程的就是在你的service或者一個方法,前調用一個方法,或者在方法後調用一個方法好比動態代理就是攔截器的簡單實現,在你調用方法前打印出字符串(或者作其它業務邏輯的操做),也能夠在你調用方法後打印出字符串,甚至在你拋出異常的時候作業務邏輯的操做。

 

1.Struts2攔截器是在訪問某個Action或Action的某個方法,字段以前或以後實施攔截,而且Struts2攔截器是可插拔的,攔截器是AOP的一種實現。

2. 攔截器棧(Interceptor Stack)。Struts2攔截器棧就是將攔截器按必定的順序聯結成一條鏈。在訪問被攔截的方法或字段時,Struts2攔截器鏈中的攔截器就會按其以前定義的順序被調用。

 

 

 

附:面向切面編程(AOP是Aspect Oriented Program的首字母縮寫) ,咱們知道,面向對象的特色是繼承、多態和封裝。而封裝就要求將功能分散到不一樣的對象中去,這在軟件設計中每每稱爲職責分配。實際上也就是說,讓不一樣的類設計不一樣的方法。這樣代碼就分散到一個個的類中去了。這樣作的好處是下降了代碼的複雜程度,使類可重用。

可是人們也發現,在分散代碼的同時,也增長了代碼的重複性。什麼意思呢?好比說,咱們在兩個類中,可能都須要在每一個方法中作日誌。按面向對象的設計方法,咱們就必須在兩個類的方法中都加入日誌的內容。也許他們是徹底相同的,但就是由於面向對象的設計讓類與類之間沒法聯繫,而不能將這些重複的代碼統一塊兒來。

也許有人會說,那好辦啊,咱們能夠將這段代碼寫在一個獨立的類獨立的方法裏,而後再在這兩個類中調用。可是,這樣一來,這兩個類跟咱們上面提到的獨立的類就有耦合了,它的改變會影響這兩個類。那麼,有沒有什麼辦法,能讓咱們在須要的時候,隨意地加入代碼呢?這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。

通常而言,咱們管切入到指定類指定方法的代碼片斷稱爲切面,而切入到哪些類、哪些方法則叫切入點。有了AOP,咱們就能夠把幾個類共有的代碼,抽取到一個切片中,等到須要時再切入對象中去,從而改變其原有的行爲。

這樣看來,AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向對象中加入特定的代碼。有了AOP,OOP變得立體了。若是加上時間維度,AOP使OOP由原來的二維變爲三維了,由平面變成立體了。從技術上來講,AOP基本上是經過代理機制實現的。

AOP在編程歷史上能夠說是里程碑式的,對OOP編程是一種十分有益的補充。

 

下面經過實例來看一下過濾器和攔截器的區別:

 

使用攔截器進行/admin 目錄下jsp頁面的過濾

 

<package name="newsDemo" extends="struts-default"

namespace="/admin">

<interceptors>

<interceptor name="auth" class="com.test.news.util.AccessInterceptor" />

<interceptor-stack name="authStack">

<interceptor-ref name="auth" />

</interceptor-stack>

</interceptors>

<!-- action -->

<action name="newsAdminView!*" class="newsAction"

method="{1}">

<interceptor-ref name="defaultStack"/>

<interceptor-ref name="authStack">

</interceptor-ref>

 

下面是我實現的Interceptor class:

 

package com.test.news.util;

import java.util.Map;

import com.opensymphony.xwork2.ActionContext;

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

import com.test.news.action.AdminLoginAction;

/**

* @author chaoyin */

 

public class AccessInterceptor extends AbstractInterceptor {

 

private static final long serialVersionUID = -4291195782860785705L;

@Override

public String intercept(ActionInvocation actionInvocation) throws Exception {

ActionContext actionContext = actionInvocation.getInvocationContext();

Map session = actionContext.getSession();

//except login action

Object action = actionInvocation.getAction();

if (action instanceof AdminLoginAction) {

return actionInvocation.invoke();

}

//check session

if(session.get("user")==null ){

return "logout";

}

return actionInvocation.invoke();//go on

}

}

過濾器:是在java web中,你傳入的request,response提早過濾掉一些信息,或者提早設置一些參數,而後再傳入servlet或者struts的 action進行業務邏輯,好比過濾掉非法url(不是login.do的地址請求,若是用戶沒有登錄都過濾掉),或者在傳入servlet或者struts的action前統一設置字符集,或者去除掉一些非法字符。主要爲了減輕服務器負載,減小壓力。

 

使用過濾器進行/admin 目錄下jsp頁面的過濾,首先在web.xml進行過濾器配置:

 

<filter>

<filter-name>access filter</filter-name>

<filter-class>

com.test.news.util.AccessFilter

</filter-class>

</filter>

<filter-mapping>

<filter-name>access filter</filter-name>

<url-pattern>/admin/*</url-pattern>

</filter-mapping>

 

下面是過濾的實現類:

package com.test.news.util;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

public class AccessFilter implements Filter {

/**

* @author chaoyin

*/

public void destroy() {

 

}

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest)arg0;

HttpServletResponse response = (HttpServletResponse)arg1;

HttpSession session = request.getSession();

if(session.getAttribute("user")== null && request.getRequestURI().indexOf("login.jsp")==-1 ){

response.sendRedirect("login.jsp");

return ;

}

filterChain.doFilter(arg0, arg1);

 

}

public void init(FilterConfig arg0) throws ServletException {

 

}

}

 

編程改變生活,改變人生

首先來看一下Servlet的過濾器內容:

 

1、Servlet過濾器的概念:

***************************************************************************************
Servlet過濾器是在Java Servlet規範2.3中定義的,它可以對Servlet容器的請求和響應對象進行檢查和修改。   
Servlet過濾器自己並不產生請求和響應對象,它只能提供過濾做用。Servlet過時可以在Servlet被調用以前檢查Request對象,修改Request Header和Request內容;在Servlet被調用以後檢查Response對象,修改Response Header和Response內容。
Servlet過時負責過濾的Web組件能夠是Servlet、JSP或者HTML文件。 
***************************************************************************************

2、Servlet過濾器的特色:
***************************************************************************************
A.Servlet過濾器能夠檢查和修改ServletRequest和ServletResponse對象
B.Servlet過濾器能夠被指定和特定的URL關聯,只有當客戶請求訪問該URL時,纔會觸發過濾器
C.Servlet過濾器能夠被串聯在一塊兒,造成管道效應,協同修改請求和響應對象
***************************************************************************************

3、Servlet過濾器的做用:
***************************************************************************************
A.查詢請求並做出相應的行動。
B.阻塞請求-響應對,使其不能進一步傳遞。
C.修改請求的頭部和數據。用戶能夠提供自定義的請求。
D.修改響應的頭部和數據。用戶能夠經過提供定製的響應版本實現。
E.與外部資源進行交互。
***************************************************************************************

4、Servlet過濾器的適用場合:
***************************************************************************************
A.認證過濾
B.登陸和審覈過濾
C.圖像轉換過濾
D.數據壓縮過濾
E.加密過濾
F.令牌過濾
G.資源訪問觸發事件過濾
H.XSL/T過濾
I.Mime-type過濾
***************************************************************************************

5、Servlet過濾器接口的構成:
***************************************************************************************
全部的Servlet過濾器類都必須實現javax.servlet.Filter接口。這個接口含有3個過濾器類必須實現的方法:
A.init(FilterConfig):
這是Servlet過濾器的初始化方法,Servlet容器建立Servlet過濾器實例後將調用這個方法。在這個方法中能夠讀取web.xml文件中Servlet過濾器的初始化參數
B.doFilter(ServletRequest,ServletResponse,FilterChain):
這個方法完成實際的過濾操做,當客戶請求訪問於過濾器關聯的URL時,Servlet容器將先調用過濾器的doFilter方法。FilterChain參數用於訪問後續過濾器
C.destroy():
Servlet容器在銷燬過濾器實例前調用該方法,這個方法中能夠釋放Servlet過濾器佔用的資源
***************************************************************************************

6、Servlet過濾器的建立步驟:
***************************************************************************************
A.實現javax.servlet.Filter接口
B.實現init方法,讀取過濾器的初始化函數
C.實現doFilter方法,完成對請求或過濾的響應
D.調用FilterChain接口對象的doFilter方法,向後續的過濾器傳遞請求或響應
E.銷燬過濾器
***************************************************************************************

7、Servlet過濾器對請求的過濾:
***************************************************************************************
A.Servlet容器建立一個過濾器實例
B.過濾器實例調用init方法,讀取過濾器的初始化參數
C.過濾器實例調用doFilter方法,根據初始化參數的值判斷該請求是否合法
D.若是該請求不合法則阻塞該請求
E.若是該請求合法則調用chain.doFilter方法將該請求向後續傳遞
***************************************************************************************

8、Servlet過濾器對響應的過濾:
***************************************************************************************
A.過濾器截獲客戶端的請求
B.從新封裝ServletResponse,在封裝後的ServletResponse中提供用戶自定義的輸出流
C.將請求向後續傳遞
D.Web組件產生響應
E.從封裝後的ServletResponse中獲取用戶自定義的輸出流
F.將響應內容經過用戶自定義的輸出流寫入到緩衝流中
G.在緩衝流中修改響應的內容後清空緩衝流,輸出響應內容
***************************************************************************************

9、Servlet過濾器的發佈:
***************************************************************************************
A.發佈Servlet過濾器時,必須在web.xml文件中加入<filter>元素和<filter-mapping>元素。
B.<filter>元素用來定義一個過濾器:
屬性 含義
filter-name 指定過濾器的名字
filter-class 指定過濾器的類名
init-param 爲過濾器實例提供初始化參數,能夠有多個
C.<filter-mapping>元素用於將過濾器和URL關聯:
屬性 含義
filter-name 指定過濾器的名字
url-pattern 指定和過濾器關聯的URL,爲」/*」表示全部URL
***************************************************************************************

11、Servlet過濾器使用的注意事項
***************************************************************************************
A.因爲Filter、FilterConfig、FilterChain都是位於javax.servlet包下,並不是HTTP包所特有的,因此其中所用到的請求、響應對象ServletRequest、ServletResponse在使用前都必須先轉換成HttpServletRequest、HttpServletResponse再進行下一步操做。
B.在web.xml中配置Servlet和Servlet過濾器,應該先聲明過濾器元素,再聲明Servlet元素
C.若是要在Servlet中觀察過濾器生成的日誌,應該確保在server.xml的localhost對應的<host>元素中配置以下<logger>元素:
<Logger className = 「org.apache.catalina.logger.FileLogger」
directory = 「logs」prefix = 「localhost_log.」suffix=」.txt」
timestamp = 「true」/>

***************************************************************************************

12、一個實例

首先來看一下web.xml的配置:

[html]view plaincopy

1.<!--請求url日誌記錄過濾器-->

2.<filter>

3.<filter-name>logfilter</filter-name>

4.<filter-class>com.weijia.filterservlet.LogFilter</filter-class>

5.</filter>

6.<filter-mapping>

7.<filter-name>logfilter</filter-name>

8.<url-pattern>/*</url-pattern>

9.</filter-mapping>

10.

11.<!--編碼過濾器-->

12.<filter>

13.<filter-name>setCharacterEncoding</filter-name>

14.<filter-class>com.weijia.filterservlet.EncodingFilter</filter-class>

15.<init-param>

16.<param-name>encoding</param-name>

17.<param-value>utf-8</param-value>

18.</init-param>

19.</filter>

20.<filter-mapping>

21.<filter-name>setCharacterEncoding</filter-name>

22.<url-pattern>/*</url-pattern>

23.</filter-mapping>


而後看一下編碼過濾器:

[java]view plaincopy

1.packagecom.weijia.filterservlet;

2.

3.importjava.io.IOException;

4.importjava.util.Enumeration;

5.importjava.util.HashMap;

6.

7.importjavax.servlet.Filter;

8.importjavax.servlet.FilterChain;

9.importjavax.servlet.FilterConfig;

10.importjavax.servlet.ServletException;

11.importjavax.servlet.ServletRequest;

12.importjavax.servlet.ServletResponse;

13.

14.publicclassEncodingFilterimplementsFilter{

15.privateStringencoding;

16.privateHashMap<String,String>params=newHashMap<String,String>();

17.//項目結束時就已經進行銷燬

18.publicvoiddestroy(){

19.System.out.println("enddotheencodingfilter!");

20.params=null;

21.encoding=null;

22.}

23.publicvoiddoFilter(ServletRequestreq,ServletResponseresp,FilterChainchain)throwsIOException,ServletException{

24.System.out.println("beforeencoding"+encoding+"filter!");

25.req.setCharacterEncoding(encoding);

26.chain.doFilter(req,resp);

27.System.out.println("afterencoding"+encoding+"filter!");

28.System.err.println("----------------------------------------");

29.}

30.

31.//項目啓動時就已經進行讀取

32.publicvoidinit(FilterConfigconfig)throwsServletException{

33.System.out.println("begindotheencodingfilter!");

34.encoding=config.getInitParameter("encoding");

35.for(Enumeration<?>e=config.getInitParameterNames();e.hasMoreElements();){

36.Stringname=(String)e.nextElement();

37.Stringvalue=config.getInitParameter(name);

38.params.put(name,value);

39.}

40.}

41.}


日誌過濾器:

[java]view plaincopy

1.packagecom.weijia.filterservlet;

2.

3.importjava.io.IOException;

4.

5.importjavax.servlet.Filter;

6.importjavax.servlet.FilterChain;

7.importjavax.servlet.FilterConfig;

8.importjavax.servlet.ServletException;

9.importjavax.servlet.ServletRequest;

10.importjavax.servlet.ServletResponse;

11.importjavax.servlet.http.HttpServletRequest;

12.

13.publicclassLogFilterimplementsFilter{

14.

15.publicFilterConfigconfig;

16.

17.publicvoiddestroy(){

18.this.config=null;

19.System.out.println("enddotheloggingfilter!");

20.}

21.

22.publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain)throwsIOException,ServletException{

23.System.out.println("beforethelogfilter!");

24.//將請求轉換成HttpServletRequest請求

25.HttpServletRequesthreq=(HttpServletRequest)req;

26.//記錄日誌

27.System.out.println("LogFilter已經截獲到用戶的請求的地址:"+hreq.getServletPath());

28.try{

29.//Filter只是鏈式處理,請求依然轉發到目的地址。

30.chain.doFilter(req,res);

31.}catch(Exceptione){

32.e.printStackTrace();

33.}

34.System.out.println("afterthelogfilter!");

35.}

36.

37.publicvoidinit(FilterConfigconfig)throwsServletException{

38.System.out.println("begindothelogfilter!");

39.this.config=config;

40.}

41.

42.}


測試Servlet:

[java]view plaincopy

1.packagecom.weijia.filterservlet;

2.

3.importjava.io.IOException;

4.

5.importjavax.servlet.ServletException;

6.importjavax.servlet.http.HttpServlet;

7.importjavax.servlet.http.HttpServletRequest;

8.importjavax.servlet.http.HttpServletResponse;

9.

10.publicclassFilterServletextendsHttpServlet{

11.

12.privatestaticfinallongserialVersionUID=1L;

13.

14.publicvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)

15.throwsServletException,IOException{

16.response.setDateHeader("expires",-1);

17.}

18.

19.publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)

20.throwsServletException,IOException{

21.}

22.

23.}

 

訪問FilterServlet

運行結果:

before the log filter!
Log Filter已經截獲到用戶的請求的地址:/FilterServlet
before encoding utf-8 filter!
after encoding utf-8 filter!
----------------------------------------
after the log filter!

咱們從運行結果能夠看到這個過濾器的調用關係:

相似於C++中的構造函數和析構函數的調用順序,

這裏咱們在web.xml中註冊的是先註冊日誌過濾器的,而後再註冊

 

當咱們從新部署應用的時候發現:

會先銷燬上次的過濾器,而後再從新註冊一下

 

下面在來看一下Servlet的監聽器

 

Servlet監聽器用於監聽一些重要事件的發生,監聽器對象能夠在事情發生前、發生後能夠作一些必要的處理。下面將介紹幾種經常使用的監聽器,以及它們都適合運用於那些環境。


分類及介紹:
1. ServletContextListener:用於監聽WEB 應用啓動和銷燬的事件,監聽器類須要實現javax.servlet.ServletContextListener 接口。

Java代碼

1.publicclassQuartzListenerimplementsServletContextListener{

2.

3.privateLoggerlogger=LoggerFactory.getLogger(QuartzListener.class);

4.

5.publicvoidcontextInitialized(ServletContextEventsce){

6.

7.}

8.

9./**

10.*在服務器中止運行的時候中止全部的定時任務

11.*/

12.@SuppressWarnings("unchecked")

13.publicvoidcontextDestroyed(ServletContextEventarg0){

14.try{

15.Schedulerscheduler=StdSchedulerFactory.getDefaultScheduler();

16.List<JobExecutionContext>jobList=scheduler.getCurrentlyExecutingJobs();

17.for(JobExecutionContextjobContext:jobList){

18.Jobjob=jobContext.getJobInstance();

19.if(jobinstanceofInterruptableJob){

20.((InterruptableJob)job).interrupt();

21.}

22.}

23.scheduler.shutdown();

24.}catch(SchedulerExceptione){

25.logger.error("shutdownschedulerhappenederror",e);

26.}

27.}

28.}

 


2. ServletContextAttributeListener:用於監聽WEB應用屬性改變的事件,包括:增長屬性、刪除屬性、修改屬性,監聽器類須要實現javax.servlet.ServletContextAttributeListener接口。

3. HttpSessionListener:用於監聽Session對象的建立和銷燬,監聽器類須要實現javax.servlet.http.HttpSessionListener接口或者javax.servlet.http.HttpSessionActivationListener接口,或者兩個都實現。

Java代碼

1./**

2.*

3.*會話監聽器

4.*<p/>

5.*

6.*/

7.publicclassSessionListenerimplementsHttpSessionListener{

8.

9.@Override

10.publicvoidsessionCreated(HttpSessionEventarg0){

11.

12.}

13.

14.@Override

15.publicvoidsessionDestroyed(HttpSessionEventevent){

16.HttpSessionsession=event.getSession();

17.Useruser=(BrsSession)session.getAttribute("currUser");

18.if(user!=null){

19.//TODOsomething

20.}

21.}

22.

23.}


4. HttpSessionActivationListener:用於監聽Session對象的鈍化/活化事件,監聽器類須要實現javax.servlet.http.HttpSessionListener接口或者javax.servlet.http.HttpSessionActivationListener接口,或者兩個都實現。

5. HttpSessionAttributeListener:用於監聽Session對象屬性的改變事件,監聽器類須要實現javax.servlet.http.HttpSessionAttributeListener接口。

部署:
監聽器的部署在web.xml文件中配置,在配置文件中,它的位置應該在過濾器的後面Servlet的前面

 

web.xml配置文件:

 

Java代碼

1.<!--Quartz監聽器-->

2.<listener>

3.<listener-class>

4.com.flyer.lisenter.QuartzListener

5.</listener-class>

6.</listener>

7.

 

Spring IOC容器基本原理aop

2.2.1 IOC容器的概念
IOC容器就是具備依賴注入功能的容器,IOC容器負責實例化、定位、配置應用程序中的對象及創建這些對象間的依賴。應用程序無需直接在代碼中new相關的對象,應用程序由IOC容器進行組裝。在SpringBeanFactoryIOC容器的實際表明者。

Spring IOC容器如何知道哪些是它管理的對象呢?這就須要配置文件,Spring IOC容器經過讀取配置文件中的配置元數據,經過元數據對應用中的各個對象進行實例化及裝配。通常使用基於xml配置文件進行配置元數據,並且Spring與配置文件徹底解耦的,能夠使用其餘任何可能的方式進行配置元數據,好比註解、基於java文件的、基於屬性文件的配置均可以。
Spring IOC容器管理的對象叫什麼呢?

2.2.2 Bean的概念
IOC容器管理的那些組成你應用程序的對象咱們就叫它BeanBean就是由Spring容器初始化、裝配及管理的對象,除此以外,bean就與應用程序中的其餘對象沒有什麼區別了。那IOC怎樣肯定如何實例化Bean、管理Bean之間的依賴關係以及管理Bean呢?這就須要配置元數據,在Spring中由BeanDefinition表明,後邊會詳細介紹,配置元數據指定如何實例化Bean、如何組裝Bean等。概念知道的差很少了,讓咱們來作個簡單的例子。

2.2.3 開始Spring Hello World之旅
1、準備須要的jar
核心jar包:從下載的spring-framework-3.0.5.RELEASE-with-docs.zipdist目錄查找以下jar
org.springframework.asm-3.0.5.RELEASE.jar
org.springframework.core-3.0.5.RELEASE.jar
org.springframework.beans-3.0.5.RELEASE.jar
org.springframework.context-3.0.5.RELEASE.jar
org.springframework.expression-3.0.5.RELEASE.jar

依賴的jar包:從下載的spring-framework-3.0.5.RELEASE-dependencies.zip中查找以下依賴jar
com.springsource.org.apache.log4j-1.2.15.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.apache.commons.collections-3.2.1.jar

2、建立標準Java工程:

3、項目搭建好了,讓咱們來開發接口,此處咱們只需實現打印「Hello World!」,因此咱們定義一個「sayHello」接口,代碼以下:

packagecom.ljq.test;

publicinterfaceHelloService {

publicvoidsayHello();

}

 

4、接口開發好了,讓咱們來經過實現接口來完成打印「Hello World!」功能;

 

packagecom.ljq.test;

publicclassHelloServiceImpl implementsHelloService{

publicvoidsayHello(){

System.out.println("Hello World!");

}

}

 

 

5、接口和實現都開發好了,那如何使用Spring IOC容器來管理它們呢?這就須要配置文件,讓IOC容器知道要管理哪些對象。讓咱們來看下配置文件helloworld.xml(放到src目錄下):

 

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation=" http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">

 

<!-- id 表示組件的名字,class表示組件類 -->

<bean id="helloService" class="com.ljq.test.HelloServiceImpl" />

 

</beans>

 

 

6、如今萬一具有,那如何獲取IOC容器並完成咱們須要的功能呢?首先應該實例化一個IOC容器,而後從容器中獲取須要的對象,而後調用接口完成咱們須要的功能,代碼示例以下:

 

packagecom.ljq.test;

importorg.junit.Test;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;

/**

* 測試

*

* @author林計欽

* @version1.0 2013-11-4 下午10:56:04

*/publicclassHelloServiceTest {

@Test

publicvoidtestHelloWorld() {

// 1、讀取配置文件實例化一個IOC容器

ApplicationContext context = newClassPathXmlApplicationContext("helloworld.xml");

// 2、從容器中獲取Bean,注意此處徹底面向接口編程,而不是面向實現

HelloService helloService = context.getBean("helloService", HelloService.class);

// 3、執行業務邏輯helloService.sayHello();

}

}

 

 

7、自此一個完整的Spring Hello World已完成,是否是很簡單,讓咱們深刻理解下容器和Bean吧。

2.2.4 詳解IOC容器
Spring IOC容器的表明就是org.springframework.beans包中的BeanFactory接口,BeanFactory接口提供了IOC容器最基本功能;而org.springframework.context包下的ApplicationContext接口擴展了BeanFactory,還提供了與Spring AOP集成、國際化處理、事件傳播及提供不一樣層次的context實現 (如針對web應用的WebApplicationContext)。簡單說, BeanFactory提供了IOC容器最基本功能,而 ApplicationContext 則增長了更多支持企業級功能支持。ApplicationContext徹底繼承BeanFactory,於是BeanFactory所具備的語義也適用於ApplicationContext
容器實現一覽:
• XmlBeanFactoryBeanFactory實現,提供基本的IOC容器功能,能夠從classpath或文件系統等獲取資源;
1File file = new File("fileSystemConfig.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);
2
Resource resource = new ClassPathResource("classpath.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);

• ClassPathXmlApplicationContextApplicationContext實現,從classpath獲取配置文件;
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
• FileSystemXmlApplicationContextApplicationContext實現,從文件系統獲取配置文件。
BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");

ApplicationContext接口獲取Bean方法簡介:
• Object getBean(String name) 根據名稱返回一個Bean,客戶端須要本身進行類型轉換;
• T getBean(String name, Class<T> requiredType) 根據名稱和指定的類型返回一個Bean,客戶端無需本身進行類型轉換,若是類型轉換失敗,容器拋出異常;
• T getBean(Class<T> requiredType) 根據指定的類型返回一個Bean,客戶端無需本身進行類型轉換,若是沒有或有多於一個Bean存在容器將拋出異常;
• Map<String, T> getBeansOfType(Class<T> type) 根據指定的類型返回一個鍵值爲名字和值爲Bean對象的Map,若是沒有Bean對象存在則返回空的Map

讓咱們來看下IOC容器究竟是如何工做。在此咱們以xml配置方式來分析一下:
1、準備配置文件:就像前邊Hello World配置文件同樣,在配置文件中聲明Bean定義也就是爲Bean配置元數據。
2、由IOC容器進行解析元數據: IOC容器的Bean Reader讀取並解析配置文件,根據定義生成BeanDefinition配置元數據對象,IOC容器根據BeanDefinition進行實例化、配置及組裝Bean
3、實例化IOC容器:由客戶端實例化容器,獲取須要的Bean

整個過程是否是很簡單,執行過程以下,其實IOC容器很容易使用,主要是如何進行Bean定義。下一章咱們詳細介紹定義Bean

2.2.5 小結
除了測試程序的代碼外,也就是程序入口,全部代碼都沒有出現Spring任何組件,並且全部咱們寫的代碼沒有實現框架擁有的接口,於是能很是容易的替換掉Spring,是否是非入侵。
客戶端代碼徹底面向接口編程,無需知道實現類,能夠經過修改配置文件來更換接口實現,客戶端代碼不須要任何修改。是否是低耦合。
若是在開發初期沒有真正的實現,咱們能夠模擬一個實現來測試,不耦合代碼,是否是很方便測試。
Bean之間幾乎沒有依賴關係,是否是很容易重用。

反射實現 AOP 動態代理模式(Spring AOP 的實現 原理)

好長時間沒有用過Spring. 忽然拿起書.我都發現本身對AOP都不熟悉了.

其實AOP的意思就是面向切面編程.

OO注重的是咱們解決問題的方法(封裝成Method),AOP注重的是許多解決解決問題的方法中的共同點,是對OO思想的一種補充!

仍是拿人家常常舉的一個例子講解一下吧:

好比說,咱們如今要開發的一個應用裏面有不少的業務方法,可是,咱們如今要對這個方法的執行作全面監控,或部分監控.也許咱們就會在要一些方法前去加上一條日誌記錄,

咱們寫個例子看看咱們最簡單的解決方案

咱們先寫一個接口IHello.java代碼以下:

1package sinosoft.dj.aop.staticaop;

2

3public interface IHello {

4

8 void sayHello(String name);

9}

10

裏面有個方法,用於輸入"Hello" 加傳進來的姓名;咱們去寫個類實現IHello接口

package sinosoft.dj.aop.staticaop;

 

public class Hello implements IHello {

 

public void sayHello(String name) {

System.out.println("Hello " + name);

}

 

}

 

如今咱們要爲這個業務方法加上日誌記錄的業務,咱們在不改變原代碼的狀況下,咱們會去怎麼作呢?也許,你會去寫一個類去實現IHello接口,並依賴Hello這個類.代碼以下:

1package sinosoft.dj.aop.staticaop;

2

3public class HelloProxy implements IHello {

4 private IHello hello;

5

6 public HelloProxy(IHello hello) {

7 this.hello = hello;

8 }

9

10 public void sayHello(String name) {

11 Logger.logging(Level.DEBUGE, "sayHello method start.");

12 hello.sayHello(name);

13 Logger.logging(Level.INFO, "sayHello method end!");

14

15 }

16

17}

18

其中.Logger類和Level枚舉代碼以下:

Logger.java

1package sinosoft.dj.aop.staticaop;

2

3import java.util.Date;

4

5public class Logger{

6

11 public static void logging(Level level, String context) {

12 if (level.equals(Level.INFO)) {

13 System.out.println(new Date().toLocaleString() + " " + context);

14 }

15 if (level.equals(Level.DEBUGE)) {

16 System.err.println(new Date() + " " + context);

17 }

18 }

19

20}

21Level.java

 

1package sinosoft.dj.aop.staticaop;

2

3public enum Level {

4 INFO,DEBUGE;

5}

6那咱們去寫個測試類看看,代碼以下:

Test.java

1package sinosoft.dj.aop.staticaop;

2

3public class Test {

4 public static void main(String[] args) {

5 IHello hello = new HelloProxy(new Hello());

6 hello.sayHello("Doublej");

7 }

8}

9運行以上代碼咱們能夠獲得下面結果:

 

Tue Mar 04 20:57:12 CST 2008 sayHello method start.

Hello Doublej

2008-3-4 20:57:12 sayHello method end!

從上面的代碼咱們能夠看出,hello對象是被HelloProxy這個所謂的代理態所建立的.這樣,若是咱們之後要把日誌記錄的功能去掉.那咱們只要把獲得hello對象的代碼改爲如下:

1package sinosoft.dj.aop.staticaop;

2

3public class Test {

4 public static void main(String[] args) {

5 IHello hello = new Hello();

6 hello.sayHello("Doublej");

7 }

8}

9

上面代碼,能夠說是AOP最簡單的實現!

可是咱們會發現一個問題,若是咱們像Hello這樣的類不少,那麼,咱們是否是要去寫不少個HelloProxy這樣的類呢.沒錯,是的.其實也是一種很麻煩的事.jdk1.3之後.jdk跟咱們提供了一個API java.lang.reflect.InvocationHandler的類. 這個類可讓咱們在JVM調用某個類的方法時動態的爲些方法作些什麼事.讓咱們把以上的代碼改一下來看看效果.

一樣,咱們寫一個IHello的接口和一個Hello的實現類.在接口中.咱們定義兩個方法;代碼以下 :

 

IHello.java

1package sinosoft.dj.aop.proxyaop;

2

3public interface IHello {

4

8 void sayHello(String name);

9

13 void sayGoogBye(String name);

14}

15

 

Hello.java

 

1package sinosoft.dj.aop.proxyaop;

2

3public class Hello implements IHello {

4

5 public void sayHello(String name) {

6 System.out.println("Hello " + name);

7 }

8 public void sayGoogBye(String name) {

9 System.out.println(name+" GoodBye!");

10 }

11}

12

咱們同樣的去寫一個代理類.只不過.讓這個類去實現java.lang.reflect.InvocationHandler接口,代碼以下:

1package sinosoft.dj.aop.proxyaop;

2

3import java.lang.reflect.InvocationHandler;

4import java.lang.reflect.Method;

5import java.lang.reflect.Proxy;

6

7public class DynaProxyHello implements InvocationHandler {

8

9

12 private Object delegate;

13

14

21 public Object bind(Object delegate) {

22 this.delegate = delegate;

23 return Proxy.newProxyInstance(

24 this.delegate.getClass().getClassLoader(), this.delegate

25 .getClass().getInterfaces(), this);

26 }

27

31 public Object invoke(Object proxy, Method method, Object[] args)

32 throws Throwable {

33 Object result = null;

34 try {

35 //執行原來的方法以前記錄日誌

36 Logger.logging(Level.DEBUGE, method.getName() + " Method end .");

37

38 //JVM經過這條語句執行原來的方法(反射機制)

39 result = method.invoke(this.delegate, args);

40 //執行原來的方法以後記錄日誌

41 Logger.logging(Level.INFO, method.getName() + " Method Start!");

42 } catch (Exception e) {

43 e.printStackTrace();

44 }

45 //返回方法返回值給調用者

46 return result;

47 }

48

49}

50

上面類中出現的Logger類和Level枚舉仍是和上一上例子的實現是同樣的.這裏就不貼出代碼了.

 

讓咱們寫一個Test類去測試一下.代碼以下:

Test.java

1package sinosoft.dj.aop.proxyaop;

2

3public class Test {

4 public static void main(String[] args) {

5 IHello hello = (IHello)new DynaProxyHello().bind(new Hello());

6 hello.sayGoogBye("Double J");

7 hello.sayHello("Double J");

8

9 }

10}

11

運行輸出的結果以下:

Tue Mar 04 21:24:03 CST 2008 sayGoogBye Method end .

Double J GoodBye!

2008-3-4 21:24:03 sayGoogBye Method Start!

Tue Mar 04 21:24:03 CST 2008 sayHello Method end .

Hello Double J

2008-3-4 21:24:03 sayHello Method Start!

因爲線程的關係,第二個方法的開始出如今第一個方法的結束以前.這不是咱們所關注的!

從上面的例子咱們看出.只要你是採用面向接口編程,那麼,你的任何對象的方法執行以前要加上記錄日誌的操做都是能夠的.(DynaPoxyHello)自動去代理執行被代理對象(Hello)中的每個方法,一個java.lang.reflect.InvocationHandler接口就把咱們的代理對象和被代理對象解藕了.可是,咱們又發現還有一個問題,這個DynaPoxyHello對象只能跟咱們去在方法先後加上日誌記錄的操做.咱們能不能把DynaPoxyHello對象和日誌操做對象(Logger)解藕呢?

結果是確定的.讓咱們來分析一下咱們的需求.

咱們要在被代理對象的方法前面或者後面去加上日誌操做代碼(或者是其它操做的代碼),

那麼,咱們能夠抽象出一個接口,這個接口裏就只有兩個方法,一個是在被代理對象要執行方法以前執行的方法,咱們取名爲start,第二個方法就是在被代理對象執行方法以後執行的方法,咱們取名爲end .接口定義以下 :

1package sinosoft.dj.aop.proxyaop;

2

3import java.lang.reflect.Method;

4

5public interface IOperation {

6

10 void start(Method method);

11

15 void end(Method method);

16}

17

咱們去寫一個實現上面接口的類.咱們把做他真正的操做者,以下面是日誌操做者的一個類:

LoggerOperation.java

package sinosoft.dj.aop.proxyaop;

 

import java.lang.reflect.Method;

 

public class LoggerOperation implements IOperation {

 

public void end(Method method) {

Logger.logging(Level.DEBUGE, method.getName() + " Method end .");

}

 

public void start(Method method) {

Logger.logging(Level.INFO, method.getName() + " Method Start!");

}

 

}

 

而後咱們要改一下代理對象DynaProxyHello中的代碼.以下:

1package sinosoft.dj.aop.proxyaop;

2

3import java.lang.reflect.InvocationHandler;

4import java.lang.reflect.Method;

5import java.lang.reflect.Proxy;

6

7public class DynaProxyHello implements InvocationHandler {

8

11 private Object proxy;

12

15 private Object delegate;

16

17

24 public Object bind(Object delegate,Object proxy) {

25

26 this.proxy = proxy;

27 this.delegate = delegate;

28 return Proxy.newProxyInstance(

29 this.delegate.getClass().getClassLoader(), this.delegate

30 .getClass().getInterfaces(), this);

31 }

32

36 public Object invoke(Object proxy, Method method, Object[] args)

37 throws Throwable {

38 Object result = null;

39 try {

40 //反射獲得操做者的實例

41 Class clazz = this.proxy.getClass();

42 //反射獲得操做者的Start方法

43 Method start = clazz.getDeclaredMethod("start",

44 new Class[] { Method.class });

45 //反射執行start方法

46 start.invoke(this.proxy, new Object[] { method });

47 //執行要處理對象的本來方法

48 result = method.invoke(this.delegate, args);

49// 反射獲得操做者的end方法

50 Method end = clazz.getDeclaredMethod("end",

51 new Class[] { Method.class });

52// 反射執行end方法

53 end.invoke(this.proxy, new Object[] { method });

54

55 } catch (Exception e) {

56 e.printStackTrace();

57 }

58 return result;

59 }

60

61}

62

而後咱們把Test.java中的代碼改一下.測試一下:

package sinosoft.dj.aop.proxyaop;

 

public class Test {

public static void main(String[] args) {

IHello hello = (IHello)new DynaProxyHello().bind(new Hello(),new LoggerOperation());

hello.sayGoogBye("Double J");

hello.sayHello("Double J");

 

}

}

結果仍是同樣的吧.

 

若是你想在每一個方法以前加上日誌記錄,而不在方法後加上日誌記錄.你就把LoggerOperation類改爲以下:

1package sinosoft.dj.aop.proxyaop;

2

3import java.lang.reflect.Method;

4

5public class LoggerOperation implements IOperation {

6

7 public void end(Method method) {

8 //Logger.logging(Level.DEBUGE, method.getName() + " Method end .");

9 }

10

11 public void start(Method method) {

12 Logger.logging(Level.INFO, method.getName() + " Method Start!");

13 }

14

15}

16

運行一下.你就會發現,每一個方法以後沒有記錄日誌了. 這樣,咱們就把代理者和操做者解藕了!

 

下面留一個問題給你們,若是咱們不想讓全部方法都被日誌記錄,咱們應該怎麼去解藕呢.?

個人想法是在代理對象的public Object invoke(Object proxy, Method method, Object[] args)方法裏面加上個if(),對傳進來的method的名字進行判斷,判斷的條件存在XML裏面.這樣咱們就能夠配置文件時行解藕了.若是有興趣的朋友能夠把操做者,被代理者,都經過配置文件進行配置 ,那麼就能夠寫一個簡單的SpringAOP框架了.

spring ioc 原理 spring aop原理

1.關於spring ioc

 這段時間也着實好好的看了下spring的相關書籍,對其也有了大概和初步的認識和理解,雖然以前也一直據說spring是一個很是優秀的開源框架,可一直沒有機會學習和使用(是否是有點落伍了?呵呵),因此呢,這段時間就重點學習了spring(一個星期的時間固然是入門級的啦~~

  你們一直都說springIOC如何如何的強大,其實我倒以爲不是IOC如何的強大,說白了IOC其實也很是的簡單。咱們先從IOC提及,這個概念實際上是從咱們日常new一個對象的對立面來講的,咱們日常使用對象的時候,通常都是直接使用關鍵字類new一個對象,那這樣有什麼壞處呢?其實很顯然的,使用new那麼就表示當前模塊已經不知不覺的和new的對象耦合了,而咱們一般都是更高層次的抽象模塊調用底層的實現模塊,這樣也就產生了模塊依賴於具體的實現,這樣與咱們JAVA中提倡的面向接口面向抽象編程是相沖突的,並且這樣作也帶來系統的模塊架構問題。很簡單的例子,咱們在進行數據庫操做的時候,老是業務層調用DAO層,固然咱們的DAO通常都是會採用接口開發,這在必定程度上知足了鬆耦合,使業務邏輯層不依賴於具體的數據庫DAO層。可是咱們在使用的時候仍是會new一個特定數據庫的DAO層,這無形中也與特定的數據庫綁定了,雖然咱們能夠使用抽象工廠模式來獲取DAO實現類,但除非咱們一次性把全部數據庫的DAO寫出來,不然在進行數據庫遷移的時候咱們仍是得修改DAO工廠類。

  那咱們使用IOC能達到什麼呢?IOC,就是DAO接口的實現再也不是業務邏輯層調用工廠類去獲取,而是經過容器(好比spring)來自動的爲咱們的業務層設置DAO的實現類。這樣整個過程就反過來,之前是咱們業務層主動去獲取DAO,而如今是DAO主動被設置到業務邏輯層中來了,這也就是反轉控制的由來。經過IOC,咱們就能夠在不修改任何代碼的狀況下,無縫的實現數據庫的換庫遷移,固然前提仍是必須得寫一個實現特定數據庫的DAO。咱們把DAO廣泛到更多的狀況下,那麼IOC就爲咱們帶來更大的方便性,好比一個接口的多個實現,咱們只須要配置一下就ok了,而不須要再一個個的寫工廠來來獲取了。這就是IOC爲咱們帶來的模塊的鬆耦合和應用的便利性。

  那爲何說IOC很簡單呢?說白了其實就是由咱們日常的new轉成了使用反射來獲取類的實例,相信任何人只要會用java的反射機制,那麼本身寫一個IOC框架也不是不可能的。好比:

……
public ObjectgetInstance(String className) throws Exception
{
  Object obj = Class.forName(className).newInstance();
  Method[] methods = obj.getClass().getMethods();
  for (Method method : methods) {
    if (method.getName().intern() == "setString") {
      method.invoke(obj, "hello world!");
    }
  }
}
……

  上面的一個方法咱們就很簡單的使用了反射爲指定的類的setString方法來設置一個hello world!字符串。其實能夠看到IOC真的很簡單,固然了IOC簡單並不表示springIOC就簡單,springIOC的功能強大就在於有一系列很是強大的配置文件維護類,它們能夠維護spring配置文件中的各個類的關係,這纔是springIOC真正強大的地方。在springBean定義文件中,不只能夠爲定義Bean設置屬性,還支持Bean之間的繼承、Bean的抽象和不一樣的獲取方式等等功能。

  下次俺再把springBean配置的相關心得和你們一塊兒分享下,若是說的很差,你們能夠提意見哦,可千萬不要仍臭雞蛋,嘿嘿~~~~


2.關於spring aop

反射實現 AOP 動態代理模式(Spring AOP 的實現 原理)
好長時間沒有用過Spring. 忽然拿起書.我都發現本身對AOP都不熟悉了.
其實AOP的意思就是面向切面編程.
OO注重的是咱們解決問題的方法(封裝成Method),AOP注重的是許多解決解決問題的方法中的共同點,是對OO思想的一種補充!
仍是拿人家常常舉的一個例子講解一下吧:
好比說,咱們如今要開發的一個應用裏面有不少的業務方法,可是,咱們如今要對這個方法的執行作全面監控,或部分監控.也許咱們就會在要一些方法前去加上一條日誌記錄,
咱們寫個例子看看咱們最簡單的解決方案
咱們先寫一個接口IHello.java代碼以下:
1package sinosoft.dj.aop.staticaop;
2
3public interface IHello {
4 /** *//**
5 * 假設這是一個業務方法
6 * @param name
7 */
8 void sayHello(String name);
9}
10
裏面有個方法,用於輸入"Hello" 加傳進來的姓名;咱們去寫個類實現IHello接口
package sinosoft.dj.aop.staticaop;

public class Hello implements IHello {

public void sayHello(String name) {
System.out.println("Hello " + name);
}

}

如今咱們要爲這個業務方法加上日誌記錄的業務,咱們在不改變原代碼的狀況下,咱們會去怎麼作呢?也許,你會去寫一個類去實現IHello接口,並依賴Hello這個類.代碼以下:
1package sinosoft.dj.aop.staticaop;
2
3public class HelloProxy implements IHello {
4 private IHello hello;
5
6 public HelloProxy(IHello hello) {
7 this.hello = hello;
8 }
9
10 public void sayHello(String name) {
11 Logger.logging(Level.DEBUGE, "sayHello method start.");
12 hello.sayHello(name);
13 Logger.logging(Level.INFO, "sayHello method end!");
14
15 }
16
17}
18
其中.Logger類和Level枚舉代碼以下:
Logger.java
1package sinosoft.dj.aop.staticaop;
2
3import java.util.Date;
4
5public class Logger{
6 /** *//**
7 * 根據等級記錄日誌
8 * @param level
9 * @param context
10 */
11 public static void logging(Level level, String context) {
12 if (level.equals(Level.INFO)) {
13 System.out.println(new Date().toLocaleString() + " " + context);
14 }
15 if (level.equals(Level.DEBUGE)) {
16 System.err.println(new Date() + " " + context);
17 }
18 }
19
20}
21Level.java

1package sinosoft.dj.aop.staticaop;
2
3public enum Level {
4 INFO,DEBUGE;
5}
6那咱們去寫個測試類看看,代碼以下:
Test.java
1package sinosoft.dj.aop.staticaop;
2
3public class Test {
4 public static void main(String[] args) {
5 IHello hello = new HelloProxy(new Hello());
6 hello.sayHello("Doublej");
7 }
8}
9運行以上代碼咱們能夠獲得下面結果:

Tue Mar 04 20:57:12 CST 2008 sayHello method start.
Hello Doublej
2008-3-4 20:57:12 sayHello method end!
從上面的代碼咱們能夠看出,hello對象是被HelloProxy這個所謂的代理態所建立的.這樣,若是咱們之後要把日誌記錄的功能去掉.那咱們只要把獲得hello對象的代碼改爲如下:
1package sinosoft.dj.aop.staticaop;
2
3public class Test {
4 public static void main(String[] args) {
5 IHello hello = new Hello();
6 hello.sayHello("Doublej");
7 }
8}
9
上面代碼,能夠說是AOP最簡單的實現!
可是咱們會發現一個問題,若是咱們像Hello這樣的類不少,那麼,咱們是否是要去寫不少個HelloProxy這樣的類呢.沒錯,是的.其實也是一種很麻煩的事.jdk1.3之後.jdk跟咱們提供了一個API java.lang.reflect.InvocationHandler的類. 這個類可讓咱們在JVM調用某個類的方法時動態的爲些方法作些什麼事.讓咱們把以上的代碼改一下來看看效果.
一樣,咱們寫一個IHello的接口和一個Hello的實現類.在接口中.咱們定義兩個方法;代碼以下 :

IHello.java
1package sinosoft.dj.aop.proxyaop;
2
3public interface IHello {
4 /** *//**
5 * 業務處理A方法
6 * @param name
7 */
8 void sayHello(String name);
9 /** *//**
10 * 業務處理B方法
11 * @param name
12 */
13 void sayGoogBye(String name);
14}
15

Hello.java

1package sinosoft.dj.aop.proxyaop;
2
3public class Hello implements IHello {
4
5 public void sayHello(String name) {
6 System.out.println("Hello " + name);
7 }
8 public void sayGoogBye(String name) {
9 System.out.println(name+" GoodBye!");
10 }
11}
12
咱們同樣的去寫一個代理類.只不過.讓這個類去實現java.lang.reflect.InvocationHandler接口,代碼以下:
1package sinosoft.dj.aop.proxyaop;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7public class DynaProxyHello implements InvocationHandler {
8
9 /** *//**
10 * 要處理的對象(也就是咱們要在方法的先後加上業務邏輯的對象,如例子中的Hello)
11 */
12 private Object delegate;
13
14 /** *//**
15 * 動態生成方法被處理事後的對象 (寫法固定)
16 *
17 * @param delegate
18 * @param proxy
19 * @return
20 */
21 public Object bind(Object delegate) {
22 this.delegate = delegate;
23 return Proxy.newProxyInstance(
24 this.delegate.getClass().getClassLoader(), this.delegate
25 .getClass().getInterfaces(), this);
26 }
27 /** *//**
28 * 要處理的對象中的每一個方法會被此方法送去JVM調用,也就是說,要處理的對象的方法只能經過此方法調用
29 * 此方法是動態的,不是手動調用的
30 */
31 public Object invoke(Object proxy, Method method, Object[] args)
32 throws Throwable {
33 Object result = null;
34 try {
35 //執行原來的方法以前記錄日誌
36 Logger.logging(Level.DEBUGE, method.getName() + " Method end .");
37
38 //JVM經過這條語句執行原來的方法(反射機制)
39 result = method.invoke(this.delegate, args);
40 //執行原來的方法以後記錄日誌
41 Logger.logging(Level.INFO, method.getName() + " Method Start!");
42 } catch (Exception e) {
43 e.printStackTrace();
44 }
45 //返回方法返回值給調用者
46 return result;
47 }
48
49}
50
上面類中出現的Logger類和Level枚舉仍是和上一上例子的實現是同樣的.這裏就不貼出代碼了.

讓咱們寫一個Test類去測試一下.代碼以下:
Test.java
1package sinosoft.dj.aop.proxyaop;
2
3public class Test {
4 public static void main(String[] args) {
5 IHello hello = (IHello)new DynaProxyHello().bind(new Hello());
6 hello.sayGoogBye("Double J");
7 hello.sayHello("Double J");
8
9 }
10}
11
運行輸出的結果以下:
Tue Mar 04 21:24:03 CST 2008 sayGoogBye Method end .
Double J GoodBye!
2008-3-4 21:24:03 sayGoogBye Method Start!
Tue Mar 04 21:24:03 CST 2008 sayHello Method end .
Hello Double J
2008-3-4 21:24:03 sayHello Method Start!
因爲線程的關係,第二個方法的開始出如今第一個方法的結束以前.這不是咱們所關注的!
從上面的例子咱們看出.只要你是採用面向接口編程,那麼,你的任何對象的方法執行以前要加上記錄日誌的操做都是能夠的.(DynaPoxyHello)自動去代理執行被代理對象(Hello)中的每個方法,一個java.lang.reflect.InvocationHandler接口就把咱們的代理對象和被代理對象解藕了.可是,咱們又發現還有一個問題,這個DynaPoxyHello對象只能跟咱們去在方法先後加上日誌記錄的操做.咱們能不能把DynaPoxyHello對象和日誌操做對象(Logger)解藕呢?
結果是確定的.讓咱們來分析一下咱們的需求.
咱們要在被代理對象的方法前面或者後面去加上日誌操做代碼(或者是其它操做的代碼),
那麼,咱們能夠抽象出一個接口,這個接口裏就只有兩個方法,一個是在被代理對象要執行方法以前執行的方法,咱們取名爲start,第二個方法就是在被代理對象執行方法以後執行的方法,咱們取名爲end .接口定義以下 :
1package sinosoft.dj.aop.proxyaop;
2
3import java.lang.reflect.Method;
4
5public interface IOperation {
6 /** *//**
7 * 方法執行以前的操做
8 * @param method
9 */
10 void start(Method method);
11 /** *//**
12 * 方法執行以後的操做
13 * @param method
14 */
15 void end(Method method);
16}
17
咱們去寫一個實現上面接口的類.咱們把做他真正的操做者,以下面是日誌操做者的一個類:
LoggerOperation.java
package sinosoft.dj.aop.proxyaop;

import java.lang.reflect.Method;

public class LoggerOperation implements IOperation {

public void end(Method method) {
Logger.logging(Level.DEBUGE, method.getName() + " Method end .");
}

public void start(Method method) {
Logger.logging(Level.INFO, method.getName() + " Method Start!");
}

}

而後咱們要改一下代理對象DynaProxyHello中的代碼.以下:
1package sinosoft.dj.aop.proxyaop;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7public class DynaProxyHello implements InvocationHandler {
8 /** *//**
9 * 操做者
10 */
11 private Object proxy;
12 /** *//**
13 * 要處理的對象(也就是咱們要在方法的先後加上業務邏輯的對象,如例子中的Hello)
14 */
15 private Object delegate;
16
17 /** *//**
18 * 動態生成方法被處理事後的對象 (寫法固定)
19 *
20 * @param delegate
21 * @param proxy
22 * @return
23 */
24 public Object bind(Object delegate,Object proxy) {
25
26 this.proxy = proxy;
27 this.delegate = delegate;
28 return Proxy.newProxyInstance(
29 this.delegate.getClass().getClassLoader(), this.delegate
30 .getClass().getInterfaces(), this);
31 }
32 /** *//**
33 * 要處理的對象中的每一個方法會被此方法送去JVM調用,也就是說,要處理的對象的方法只能經過此方法調用
34 * 此方法是動態的,不是手動調用的
35 */
36 public Object invoke(Object proxy, Method method, Object[] args)
37 throws Throwable {
38 Object result = null;
39 try {
40 //反射獲得操做者的實例
41 Class clazz = this.proxy.getClass();
42 //反射獲得操做者的Start方法
43 Method start = clazz.getDeclaredMethod("start",
44 new Class[] { Method.class });
45 //反射執行start方法
46 start.invoke(this.proxy, new Object[] { method });
47 //執行要處理對象的本來方法
48 result = method.invoke(this.delegate, args);
49// 反射獲得操做者的end方法
50 Method end = clazz.getDeclaredMethod("end",
51 new Class[] { Method.class });
52// 反射執行end方法
53 end.invoke(this.proxy, new Object[] { method });
54
55 } catch (Exception e) {
56 e.printStackTrace();
57 }
58 return result;
59 }
60
61}
62
而後咱們把Test.java中的代碼改一下.測試一下:
package sinosoft.dj.aop.proxyaop;

public class Test {
public static void main(String[] args) {
IHello hello = (IHello)new DynaProxyHello().bind(new Hello(),new LoggerOperation());
hello.sayGoogBye("Double J");
hello.sayHello("Double J");

}
}
結果仍是同樣的吧.

若是你想在每一個方法以前加上日誌記錄,而不在方法後加上日誌記錄.你就把LoggerOperation類改爲以下:
1package sinosoft.dj.aop.proxyaop;
2
3import java.lang.reflect.Method;
4
5public class LoggerOperation implements IOperation {
6
7 public void end(Method method) {
8 //Logger.logging(Level.DEBUGE, method.getName() + " Method end .");
9 }
10
11 public void start(Method method) {
12 Logger.logging(Level.INFO, method.getName() + " Method Start!");
13 }
14
15}
16

相關文章
相關標籤/搜索