黑馬程序員-java-高新技術下《九》

                   ——Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! ——

一:動態代理技術

1.程序中的代理

   爲具有相同接口的目標類的各個方法,添加一些系統功能,如日誌,異常處理,計算方法運行的

   時間,事務管理等等,都可以交給另一個類去實現這些功能,該類稱爲代理類。

   注意:爲了讓代理類共享目標類中的各個方法,可以讓代理類實現和目標類相同的接口。

public class AProxy {
    //AProxy類爲A的代理類,可以計算sayHi方法的運行時間
    public  void getTime()
    {
        //方法開始前時間
         new A().sayHi();
        //方法結束後時間
    }
}
class A
{
    void sayHi()
    {
        System.out.println("hi,everyone!");
    }
}

2.代理架構圖

  

   它採用工廠模式和配置文件的方式進行管理,這樣就不需要修改客戶端程序,通過

   配置文件指定使用目標類,還是代理類。這樣做添加/去掉系統功能變得簡單。

   當我們需要測試系統的性能時,可以使用代理類,客戶要使用時可以使用目標類

3.AOP(Aspect oriented program)

   交叉業務:要處理不同層面的業務(service),稱爲交叉業務,如安全,事務,日誌等。

                                  安全       事務         日誌
    StudentService  ------|----------|------------|-------------
    CourseService   ------|----------|------------|-------------
    MiscService       ------|----------|------------|-------------

   用具體的程序代碼描述交叉業務:
     method1         method2          method3
     {                      {                       { 
       ------------------------------------------------------切面
          ....            ....              ......
       ------------------------------------------------------切面
      }                       }                       }

   面向方面的編程(簡稱AOP),AOP的目標就是要使交叉業務模塊化。

   可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:
       ------------------------------------------------------切面
       func1         func2            func3
        {             {                { 
          ....            ....              ......
         }             }                }
      ------------------------------------------------------切面

    這裏就是使用代理技術解決這種問題,代理是實現AOP功能的核心和關鍵技術。
4.動態代理技術

  •   要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理方式,將是一件非常麻煩的事情!

  •   JVM提供了動態代理類(Proxy)。即可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類。這裏是僅指具有接口的代理類和目標類。

  •   對於沒有接口的目標類,CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫。

   代理方法中的有如下四個位置加上系統功能代碼:

     1.在調用目標方法之前
     2.在調用目標方法之後
     3.在調用目標方法前後
     4.在處理目標方法異常的catch塊中

class proxy{
    void sayHello(){
        ………//1
        try{
            target.sayHello();
        }catch(Exception e){
            ………//4
        }
        …………//2
    }
}

5.Proxy(代理類)

      |-java.lang.reflect.Proxy

      Proxy提供了創建動態代理類和實例的靜態方法,它也是這些創建出來類的超(父)類。

      構造方法:

            protected  Proxy(InvocationHandler  h);//使用其調用處理程序的指定值從子類(通常爲動態代理類)構建新的 Proxy 實例

      成員方法:

            static Class<?> getProxyClass(ClassLoader loader,Class<?> interfaces);//創建一個具有接口的動態代理類。

            static Object newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);//返回一個代理類實例對象

  創建代理類及其實例對象:

  法一:先創建代理類,再用代理類的構造方法創建實例對象

public static void main(String[] args) {
        // 獲得一個集合接口的代理類,爲其指定接口和類加載器
        //通常loader,interface是一樣的字節碼產生的
        Class  clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        System.out.println(clazzProxy1.getName());
        //創建一個代理類實例
        //注意:clazzProxy1.newInstance()調用的是它的無參構造方法創建實例,但是它沒有無參的構造方法
        //通過有參構造方法創建實例
        Constructor ct=clazzProxy1.getConstructor(InvocationHandler.class);
        class MyinvocationHandler implements InvocationHandler
        {
             @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                 return null;
            }
         }
        Collection proxy1=(Collection) ct.newInstance(new MyinvocationHandler());
     }}

   法二:用new方法直接創建代理類實例對象

Object target=new Object ();
Object proxy1=Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler(){

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});

Class<?> []cs={Collection.class};
Collection proxy3=(Collection)Proxy.newProxyInstance
        (Collection.class.getClassLoader(),
          cs,new MyinvocationHandler());
Collection proxy4=(Collection)Proxy.newProxyInstance
        (Collection.class.getClassLoader(),
         new Class[] {Collection.class},
         new MyinvocationHandler());

6.InvocationHandler(接口)

   成員方法:

        Object invoke(Object proxy, Method method, Object[] args);//在代理實例上處理方法調用並返回結果。

        事實上,Proxy代理類對象調用(該接口上的)方法時,其實是去調用了handler的

        invoke方法。對於不是該接口上的方法,如getClass(),則調用它本身的,不用委託handler去調用invoke方法。

7.代理類的模式

   以面向對象的思想處理AOP(面向方面編程)的業務,即在實現代理的系統功能時,將目標類對象和接口(該接口通常以實現接口某個類的方式傳入,實現接口裏的方法(系統功能)的類)作爲參數傳入一個要實現系統功能的方法中。

ArrayList al=new ArrayList();
        Collection proxy4=(Collection)getProxy(al,new Myadvice());
        proxy4.add("hq");
    //以下是封裝了實現代理功能的一個方法
    //參數:目標類對象,和接口(實現代理功能的類,類裏有實現功能的方法),該接口通常以實現接口某個類的方式傳入。
    private static Object getProxy(final Object target,final interfaceAdvice advice) {
         Object proxy=Proxy.newProxyInstance
                (target.getClass().getClassLoader(),
                 target.getClass().getInterfaces(),
                 new InvocationHandler(){
                    
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                         advice.beforeMethod(method);
                         Object obj=method.invoke(target, args);
                         advice.afterMethod(method);
                         return obj;
                    }
                });
         return proxy;
    }

8.實現AOP功能的封裝與配置

工廠類BeanFactory負責創建目標類或代理類的實例對象,並通過配置文件實現切換。其getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則,返回該類實例對象的getProxy方法返回的對象。
BeanFactory的構造方法接收代表配置文件的輸入流對象,配置文件格式如下:
    #xxx=java.util.ArrayList
    xxx=cn.itcast.ProxyFactoryBean
    xxx.target=java.util.ArrayList
    xxx.advice=cn.itcast.MyAdvice
ProxyFacotryBean充當封裝生成動態代理的工廠,需要爲工廠類提供哪些配置參數信息?
目標:target
通知:advice
編寫客戶端應用:
編寫實現Advice接口的類和在配置文件中進行配置
調用BeanFactory獲取對象

 二:線程同步與併發

1.Timer類

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {

    public static void main(String[] args) throws Exception{
        /*
         * Timer類的演示:ava.util.Timer類是一個定時器,可以讓它定時做某件事情,
         * 方法:schedule(task,delay,period);//在delay毫秒後執行task,並以period爲週期循環執行。
         *       
         * */
        Timer t=new Timer();
//        t.schedule(new TimerTask ()
//        {
//            @Override
//            public void run() {
//                System.out.println("bombing!!!"); 
//             }
//         }, 3000);
          
        t.schedule(new MyTimerTask(), 3000);
        //每隔一秒,打印出時間
        while(true)
        {
            System.out.println(new Date().getSeconds());
            Thread.currentThread().sleep(1000);
        }
      }
}
//實現了每3秒爆炸,每2秒爆炸,並以此循環
//寫成類的原因是:我們要不停的使用該類中的run代碼,
//這樣可以通過不停的new對象出來重複使用run代碼
//int i寫成靜態的原因是:new的對象要共享數據i,所以寫成靜態
//i=(i+1)%count,的結果是i以count的爲週期,不停循環。
//注意:這裏不能寫成TimerDemo的內部類,因爲一個非靜態的內部類相當於其他非靜態的方法,不能在
//     裏面聲明靜態變量(static int i)!!或靜態方法!!
class MyTimerTask extends TimerTask {
    static int i=0;
    @Override
    public void run() {
            System.out.println("bombing!!!");
            new Timer().schedule(new MyTimerTask() , (i+2)*1000);
            i=(i+1)%2;
     }
}

2.多個線程訪問共享對象和數據的方式

    2.1 如果每個線程執行的代碼相同,可以使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,買票系統就可以這麼做。

    2.2 如果每個線程執行的代碼不同,這時候需要用不同的Runnable對象,有如下兩種方式來實現這些Runnable對象之間的數據共享:

           a.將共享數據封裝在另外一個對象中,然後將這個對象逐一傳遞給各個Runnable對象。每個線程對共享數據的操作方法也分配到那個對象身上去完成,

              這樣容易實現針對該數據進行的各個操作的互斥和通信。

           b.將這些Runnable對象作爲某一個類中的內部類,共享數據作爲這個外部類中的成員變量,每個線程對共享數據的操作方法也分配給外部類,以便實現

             對共享數據進行的各個操作的互斥和通信,作爲內部類的各個Runnable對象調用外部類的這些方法。

           上面兩種方式的組合:將共享數據封裝在另外一個對象中,每個線程對共享數據的操作方法也分配到那個對象身上去完成,對象作爲這個外部類中的成員變量或方法中的局部變量

                                       每個線程的Runnable對象作爲外部類中的成員內部類或局部內部類。

      總之,要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較容易實現它們之間的同步互斥和通信。

      極端且簡單的方式,即在任意一個類中定義一個static的變量,這將被所有線程共享。

3.ThreadLoal解決線程同步問題(http://blog.csdn.net/qjyong/article/details/2158097)

      對於多線程資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。

      前者僅提供一份變量,讓不同的線程排隊訪問,而後者爲每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

    ThreadLocal並不是一個Thread,而是Thread的局部變量,也許把它命名爲線程局部變量(ThreadLocalVariable)更容易讓人理解一些。

    ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

  • void set(Object value)

    設置當前線程的線程局部變量的值。

  • public Object get()

    該方法返回當前線程所對應的線程局部變量。

  • public void remove()

    將當前線程局部變量的值刪除,目的是爲了減少內存的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,

    對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。

  • protected Object initialValue()

    返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,

    在線程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

    值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。

    API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

public class SequenceNumber {

①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值

private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){

public Integer initialValue(){

return 0;

}

};

  

②獲取下一個序列值

public int getNextNum(){

seqNum.set(seqNum.get()+1);

return seqNum.get();

}

  

public static void main(String[] args)

{

SequenceNumber sn = new SequenceNumber();

③ 3個線程共享sn,各自產生序列號

TestClient t1 = new TestClient(sn);

TestClient t2 = new TestClient(sn);

TestClient t3 = new TestClient(sn);

t1.start();

t2.start();

t3.start();

}

private static class TestClient extends Thread

{

private SequenceNumber sn;

public TestClient(SequenceNumber sn) {

this.sn = sn;

}

public void run()

{

for (int i = 0; i < 3; i++) {④每個線程打出3個序列值

System.out.println("thread["+Thread.currentThread().getName()+

"] sn["+sn.getNextNum()+"]");

}

}

}

}

通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產生一組序列號,在③處,我們生成3個TestClient,它們共享同一個SequenceNumber實例。運行以上代碼,在控制檯上輸出以下的結果:

thread[Thread-2] sn[1]

thread[Thread-0] sn[1]

thread[Thread-1] sn[1]

thread[Thread-2] sn[2]

thread[Thread-0] sn[2]

thread[Thread-1] sn[2]

thread[Thread-2] sn[3]

thread[Thread-0] sn[3]

thread[Thread-1] sn[3]

  小結

      ThreadLocal是解決線程安全問題一個很好的思路,它通過爲每個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。
      在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。

4.Executors類

    |-java.util.concurrent.Executors 它可以提供了多種功能的線程池
   靜態方法:

            ExecutorService  newFixedThreadPool(int nThreads); //創建固定大小的線程池

            ExecutorService  newCachedThreadPool();//創建緩存線程池

            ExecutorService  newSingleThreadExecutor();//創建單一線程池

      ScheduledExecutorService  newScheduledThreadPool(int);//創建定時處理的線程池

5.ExecutorService

  成員方法:

     void shutdown();//對於執行完所有任務的線程會處於wait狀態,所以最後需要
                                      Executors類的shutdown方法銷燬執行完任務的線程

        void execute(Runnable command);//可以添加多個任務

        Future<T> submit(Callable<T> task);//在task裏會調用call方法,將結果返回給future

6.ScheduledExecutorService

     schedule(Runnable command, long delay, TimeUnit unit);//創建並執行在給定延遲後啓用的一次性操作

            線程池啓動定時器
            調用ScheduledExecutorService的schedule方法,返回的ScheduleFuture對象可以取消任務。
            支持間隔重複任務的定時方式,不直接支持絕對定時方式,需要轉換成相對時間方式。

7.CompletionService<V>

   所有已知實現類:ExecutorCompletionService   它是ExecutorService裏submit方法的加強版,可以submit多個Callable<T> task,使用者take會按照完成這些任務的順序處理它們的結果(get方法返回結果)。

      成員方法:

Future<Vtake();//獲取並移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則等待。

Future<Vsubmit(Callable<V> task);//提交要執行的值返回任務,並返回表示掛起的任務結果的 Future。

//ExecutorService類下的<T> Future<T> submit(Callable<T> task) 方法使用
         ExecutorService pool= Executors.newSingleThreadExecutor();
         Future<String> future=pool.submit(new Callable<String>() {

            @Override
            public String  call() throws Exception {
                // TODO Auto-generated method stub
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(3000);
                 
                return "哈哈哈";
            }
        });
      System.out.println("等待結果");
      System.out.println("結果:"+future.get());
      //ExecutorCompletionService類演示
      CompletionService<Integer> service=new ExecutorCompletionService<Integer>(Executors.newFixedThreadPool(3));
      //submit 10個task
      for(int i=0;i<10;i++)
      {
          final int t=i+1;
          service.submit(new Callable<Integer>() {
            
            @Override
            public Integer call() throws Exception {
                 Thread.sleep(new Random().nextInt(5)*1000);
                return t;
            }
        });
      }
      //按先後順序取出結果
      for(int j=0;j<10;j++)
      {
          System.out.println(service.take().get());
      }

     

//線程池的演示:Executors類下(靜態方法)提供了多種功能的線程池
      /*  注意:可以添加任意個任務進去.(ExecutorService類下的void execute(Runnable command)方法)
       *  創建固定大小的線程池:Executors.newFixedThreadPool(3);3爲自動啓動3個線程執行任務
                      創建緩存線程池:Executors.newCachedThreadPool();//系統會根據你添加多少任務智能分配多少個線程幫你執行
                      創建單一線程池:Executors.newSingleThreadExecutor();//系統會保證始終一個線程幫你執行任務,如果該線程死了,系統會創建出另一個線程代替。

創建定時處理的線程池:Executors.newScheduledThreadPool(3).schedule()方法     
                     注意:對於執行完所有任務的線程會處於wait狀態,所以最後需要
               Executors類的shutdown方法銷燬執行完任務的線程。      
       * */  
        ExecutorService pool=Executors.newFixedThreadPool(3);
        Executors.newCachedThreadPool();
        Executors.newSingleThreadExecutor();
        //添加任務
        for(int i=0;i<5;i++)
        {
            final int task =i+1;
            pool.execute(new Runnable() {
                
                @Override
                public void run() {
                    for(int j=0;j<10;j++)
                    System.out.println(Thread.currentThread().getName()+"執行第"+task+"個任務:"+(j+1));
                }
            });
         }
        System.out.println("shutdown");
        pool.shutdown();

 8.java.util.concurrent.atomic下

  有很多對基本數據的原子(同步)操作,甚至是類中的成員變量

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章