1. 程式人生 > >Java高階特性(動態代理和反射)

Java高階特性(動態代理和反射)

目錄

4天 java高階特性增強

今天內容安排:

1、掌握多執行緒

2、掌握併發包下的佇列

3、瞭解JMS

4、掌握JVM技術

5、掌握反射和動態代理

Øjava多執行緒增強

通俗來講:應用程式就是一個程序。

不管是我們開發的應用程式,還是我們執行的其他的應用程式,都需要先把程式安裝在本地的硬碟上。然後找到這個程式的啟動檔案,啟動程式的時候,其實是電腦把當前的這個程式載入到記憶體中,在記憶體中需要給當前的程式分配一段獨立的執行空間。這片空間就專門負責當前這個程式的執行。

    不同的應用程式執行的過程中都需要在記憶體中分配自己獨立的執行空間,彼此之間不會相互的影響。我們把每個獨立應用程式在記憶體的獨立空間稱為當前應用程式執行的一個程序。

程序:它是記憶體中的一段獨立的空間,可以負責當前應用程式的執行。當前這個程序負責排程當前程式中的所有執行細節。

    啟動的QQ聊天軟體,需要和多個人進行聊天。這時多個人之間是不能相互影響,但是它們都位於當前QQ這個軟體執行時所分配的記憶體的獨立空間中。

    在一個程序中,每個獨立的功能都需要獨立的去執行,這時又需要把當前這個程序劃分成多個執行區域,每個獨立的小區域(小單元)稱為一個執行緒。

執行緒:它是位於程序中,負責當前程序中的某個具備獨立執行資格的空間。程序中分為執行緒和堆記憶體區。

我們自己的程式設計中,開啟執行緒的方式,main()方法會開啟一個執行緒執行緒類.start()開啟一個執行緒。

程序是負責整個程式的執行,而執行緒是程式中具體的某個獨立功能的執行。一個程序中至少應該有一個執行緒。

    現在的作業系統基本都是多使用者,多工的作業系統。每個任務就是一個程序。而在這個程序中就會有執行緒。

    真正可以完成程式執行和功能的實現靠的是程序中的執行緒。

多執行緒:在一個程序中,我們同時開啟多個執行緒,讓多個執行緒同時去完成某些任務(功能)。

(比如後臺服務系統,就可以用多個執行緒同時響應多個客戶的請求)

多執行緒的目的:提高程式的執行效率。

    cpu線上程中做時間片的切換。

    其實真正電腦中的程式的執行不是同時在執行的。CPU負責程式的執行,而CPU在執行程式的過程中某個時刻點上,它其實只能執行一個程式。而不是多個程式。而CPU它可以在多個程式之間進行高速的切換。而切換頻率和速度太快,導致人的肉眼看不到。

每個程式就是程序, 而每個程序中會有多個執行緒,而CPU是在這些執行緒之間進行切換。

瞭解了CPU對一個任務的執行過程,我們就必須知道,多執行緒可以提高程式的執行效率,但不能無限制的開執行緒。

1、繼承Thread的方式

       見程式碼MyThreadWithExtends

程式碼中,試用thread.start()就是開啟一個執行緒的意思,開啟後會自動呼叫run()方法,run()方法中,就是我們的業務邏輯。

如果是thread.run()那相當於在main這個執行緒中,呼叫一個方法,不會開啟新的執行緒。

2、宣告實現 Runnable 介面的方式

       見程式碼MyThreadWithImpliment

注意,如果是利用這種實現runnable藉口的方式得到的類,新建執行緒時時上面的方式

new threadnew 實現的runnable藉口的類,執行緒名)

直白的說就是:我synchronized我拿到了鎖,得等我用完我就放開了,你們才能再去用這個鎖(物件)。

加同步格式:

              synchronized(需要一個任意的物件(鎖) ){

                     程式碼塊中放操作共享資料的程式碼。

              }

       見程式碼MySynchronized

Ø synchronized的缺陷

synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。

如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖並執行該程式碼塊時,其他想用通一把鎖的執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:

  1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;

2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。

例子1:

  如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。

  因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

例子2:

當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。

  但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:

如果多個執行緒都只是進行讀操作,當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。

  因此就需要一種機制來使得多個執行緒都只是進行讀操作時,執行緒之間不會發生衝突,通過Lock就可以辦到。

  另外,通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。

  總的來說,也就是說Lock提供了比synchronized更多的功能。

Ø locksynchronized的區別

  1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Locksynchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized程式碼塊執行完之後,系統會自動讓執行緒釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

Ø java.util.concurrent.locks包下常用的類

²  Lock

  首先要說明的就是Lock,通過檢視Lock的原始碼可知,Lock是一個介面:

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    }

Lock介面中每個方法的使用:

lock()、tryLock()、tryLock(longtime, TimeUnit unit)、lockInterruptibly()是用來獲取鎖的。    unLock()方法是用來釋放鎖的。

四個獲取鎖方法的區別:

  lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待。

由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。

tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

  tryLock(longtime, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

  lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有等待,那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待過程。

  注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。

  因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

²   ReentrantLock

直接使用lock介面的話,我們需要實現很多方法,不太方便,ReentrantLock是唯一實現了Lock介面的類,並且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入鎖”。

以下是ReentrantLock的使用案例:

  例子1,lock()的正確使用方法

       見程式碼MyLockTest

例子2,tryLock()的使用方法

見程式碼MyTryLock

例子3,lockInterruptibly()響應中斷的使用方法:

見程式碼MyInterruptibly

  ReadWriteLock也是一個介面,在它裡面只定義了兩個方法:

public interface ReadWriteLock {

    /**

     * Returns the lock used for reading.

     *

     * @return the lock used for reading.

     */

    Lock readLock();

    /**

     * Returns the lock used for writing.

     *

     * @return the lock used for writing.

     */

    Lock writeLock();

}

  一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將檔案的讀寫操作分開,分成2個鎖來分配給執行緒,從而使得多個執行緒可以同時進行讀操作。下面的ReentrantReadWriteLock實現了ReadWriteLock介面。

²  ReentrantReadWriteLock

  ReentrantReadWriteLock裡面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。

下面通過幾個例子來看一下ReentrantReadWriteLock具體用法。

例子1:假如有多個執行緒要同時進行讀操作的話,先看一下synchronized達到的效果

見程式碼MySynchronizedReadWrite

例子2:改成用讀寫鎖的話:

見程式碼MyReentrantReadWriteLock

注意:

  不過要注意的是,如果有一個執行緒已經佔用了讀鎖,則此時其他執行緒如果要申請寫鎖,則申請寫鎖的執行緒會一直等待釋放讀鎖。

如果有一個執行緒已經佔用了寫鎖,則此時其他執行緒如果申請寫鎖或者讀鎖,則申請的執行緒會一直等待釋放寫鎖。

²  Lock和synchronized的選擇

  1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;

  2)synchronized在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

  3)Lock可以讓等待鎖的執行緒響應中斷,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;

  4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

  5)Lock可以提高多個執行緒進行讀操作的效率。

  在效能上來說,如果競爭資源不激烈,兩者的效能是差不多的,而當競爭資源非常激烈時(即有大量執行緒同時競爭),此時Lock的效能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

Øjava併發包

       JDK5.0以後的版本都引入了高階併發特性,大多數的API在java.util.concurrent 包中,是專門用於多執行緒發程式設計的,充分利用了現代多處理器和多核心繫統的功能以編寫大規模併發應用程式。主要包含原子量、併發集合、同步器、可重入鎖,並對執行緒池的構造提供

了強力的支援。

       執行緒池

²  執行緒池的5種建立方式:

1、  SingleThread Executor : 只有一個執行緒的執行緒池,因此所有提交的任務是順序執行,

程式碼:Executors.newSingleThreadExecutor()

2、  CachedThread Pool : 執行緒池裡有很多執行緒需要同時執行,老的可用執行緒將被新的任務觸發重新執行,如果執行緒超過60秒內沒執行,那麼將被終止並從池中刪除

程式碼:Executors.newCachedThreadPool()

3、  FixedThread Pool : 擁有固定執行緒數的執行緒池,如果沒有任務執行,那麼執行緒會一直等待

程式碼:Executors.newFixedThreadPool(4)

在建構函式中的引數4是執行緒池的大小,你可以隨意設定,也可以和cpu的核心數量保持一致,獲取cpu的核數量intcpuNums = Runtime.getRuntime().availableProcessors();

4、  ScheduledThread Pool : 用來排程即將執行的任務的執行緒池,就是可以安排時間來執行程式。可能不是直接執行, 每隔5分鐘執行一次任務,或者一次執行5個任務間隔4分鐘...。。。。 策略型的

程式碼:Executors.newScheduledThreadPool()

5、  SingleThread Scheduled Pool : 只有一個執行緒,用來排程任務在指定時間執行,程式碼:Executors.newSingleThreadScheduledExecutor()

²  執行緒池的使用

(1、)提交 Runnable ,任務完成後Future 物件返回 null

呼叫excute,提交任務, 匿名Runable重寫run方法,run方法裡是業務邏輯

見程式碼:ThreadPoolWithRunable

新建的任務,就是一個執行緒,

new runnable()就是一個執行緒

 

(2、)提交 Callable,該方法返回一個Future 例項表示任務的結果。

呼叫submit提交任務, 匿名Callable,重寫call方法, 有返回值, submit.get()獲取任務的返回值

見程式碼:ThreadPoolWithcallable

.2.     java併發包訊息佇列及在開源軟體中的應用

BlockingQueue也是java.util.concurrent下的主要用來控制執行緒同步的工具

主要的方法是:put、take一對阻塞存取;add、poll一對非阻塞存取。

       插入:

              1)add(anObject):anObject加到BlockingQueue,即如果BlockingQueue可以容納,則返回true,否則丟擲異常,不好

        2)offer(anObject):表示如果可能的話,anObject加到BlockingQueue,即如果BlockingQueue可以容納,則返回true,否則返回false.

        3)put(anObject):anObject加到BlockingQueue,如果BlockQueue沒有空間,則呼叫此方法的執行緒被阻斷直到BlockingQueue裡面有空間再繼續, 有阻塞, 放不進去就等待

讀取:

 4)poll(time):取走BlockingQueue裡排在首位的物件,若不能立即取出,則可以等time引數規定的時間,取不到時返回null;取不到返回null

        5)take():取走BlockingQueue裡排在首位的物件,BlockingQueue為空,阻斷進入等待狀態直到Blocking有新的物件被加入為止; 阻塞, 取不到就一直等

其他

intremainingCapacity();返回佇列剩餘的容量,在佇列插入和獲取的時候,不要瞎搞,數據可能不準, 不能保證資料的準確性

booleanremove(Object o); 從佇列移除元素,如果存在,即移除一個或者更多,佇列改  變了返回true

public booleancontains(Object o); 檢視佇列是否存在這個元素,存在返回true

intdrainTo(Collection<? super E> c); //移除此佇列中所有可用的元素,並將它們新增到給定 collection 中。取出放到集合中

intdrainTo(Collection<? super E> c, int maxElements); 和上面方法的區別在於,指定了移       動的數量; 取出指定個數放到集合

BlockingQueue有四個具體的實現類,常用的兩種實現類為:

1、ArrayBlockingQueue:一個由陣列支援的有界阻塞佇列,規定大小的BlockingQueue,其建構函式必須帶一個int引數來指明其大小.其所含的物件是以FIFO(先入先出)順序排序的。

2、LinkedBlockingQueue:大小不定的BlockingQueue,若其建構函式帶一個規定大小的引數,生成的BlockingQueue有大小限制,若不帶大小引數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的物件是以FIFO(先入先出)順序排序的。

       LinkedBlockingQueue可以指定容量,也可以不指定,不指定的話,預設最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在佇列滿的時候會阻塞直到有佇列成員被消費,take方法在佇列空的時候會阻塞,直到有佇列成員被放進來。

LinkedBlockingQueue和ArrayBlockingQueue區別:

LinkedBlockingQueue和ArrayBlockingQueue比較起來,它們背後所用的資料結構不一樣,導致LinkedBlockingQueue的資料吞吐量要大於ArrayBlockingQueue,但線上程數量很大時其效能的可預見性低於ArrayBlockingQueue.

生產者消費者的示例程式碼:

見程式碼(生產者,就是向佇列裡填資料的。)

消費者的程式碼

佇列使用例項!!!!!

Øjava併發程式設計的一些總結

有些開發者圖省事,遇到需要多執行緒處理的地方,直接new Thread(...).start(),對於一般場景是沒問題的,但如果是在併發請求很高的情況下,就會有些隱患:

  • 新建執行緒的開銷。執行緒雖然比程序要輕量許多,但對於JVM來說,新建一個執行緒的代價還是挺大的,決不同於新建一個物件
  • 資源消耗量。沒有一個池來限制執行緒的數量,會導致執行緒的數量直接取決於應用的併發量,這樣有潛在的執行緒資料巨大的可能,那麼資源消耗量將是巨大的
  • 穩定性。當執行緒數量超過系統資源所能承受的程度,穩定性就會成問題

在每個需要多執行緒處理的地方,不管併發量有多大,需要考慮執行緒的執行策略

  • 任務以什麼順序執行
  • 可以有多少個任務併發執行
  • 可以有多少個任務進入等待執行佇列
  • 系統過載的時候,應該放棄哪些任務?如何通知到應用程式?
  • 一個任務的執行前後應該做什麼處理

不管是通過Executors建立執行緒池,還是通過Spring來管理,都得清楚知道有哪幾種執行緒池:

  • FixedThreadPool:定長執行緒池,提交任務時建立執行緒,直到池的最大容量,如果有執行緒非預期結束,會補充新執行緒
  • CachedThreadPool:可變執行緒池,它猶如一個彈簧,如果沒有任務需求時,它回收空閒執行緒,如果需求增加,則按需增加執行緒,不對池的大小做限制
  • SingleThreadExecutor:單執行緒。處理不過來的任務會進入FIFO佇列等待執行
  • SecheduledThreadPool:週期性執行緒池。支援執行週期性執行緒任務

其實,這些不同型別的執行緒池都是通過構建一個ThreadPoolExecutor來完成的,所不同的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory這麼幾個引數。具體可以參見JDK DOC

由以上執行緒池型別可知,除了CachedThreadPool其他執行緒池都有飽和的可能,當飽和以後就需要相應的策略處理請求執行緒的任務,比如,達到上限時通過ThreadPoolExecutor.setRejectedExecutionHandler方法設定一個拒絕任務的策略,JDK提供了AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy幾種策略,具體差異可見JDK DOC

多執行緒任務設計上儘量使得各任務是獨立無依賴的,所謂依賴性可兩個方面:

  • 執行緒之間的依賴性。如果執行緒有依賴可能會造成死鎖或飢餓
  • 呼叫者與執行緒的依賴性。呼叫者得監視執行緒的完成情況,影響可併發量

當然,在有些業務裡確實需要一定的依賴性,比如呼叫者需要得到執行緒完成後結果,傳統的Thread是不便完成的,因為run方法無返回值,只能通過一些共享的變數來傳遞結果,但在Executor框架裡可以通過FutureCallable實現需要有返回值的任務,當然執行緒的非同步性導致需要有相應機制來保證呼叫者能等待任務完成,關於FutureCallable的用法前文已講解;

       JMS即Java訊息服務(JavaMessage Service應用程式介面是一個Java平臺中關於面向訊息中介軟體(MOM)的API,用於在兩個應用程式之間,或分散式系統中傳送訊息,進行非同步通訊。Java訊息服務是一個與具體平臺無關的API,絕大多數MOM提供商都對JMS提供支援。

       JMS是一種與廠商無關的 API,用來訪問訊息收發系統訊息。它類似於JDBC(JavaDatabase Connectivity):這裡,JDBC是可以用來訪問許多不同關係資料庫的 API,而 JMS 則提供同樣與廠商無關的訪問方法,以訪問訊息收發服務。許多廠商都支援 JMS,包括 IBM 的MQSeries、BEA的 Weblogic JMS service和 Progress 的SonicMQ,這只是幾個例子。 JMS 使您能夠通過訊息收發服務(有時稱為訊息中介程式或路由器)從一個 JMS客戶機向另一個 JMS客戶機發送訊息。訊息是 JMS 中的一種型別物件,由兩部分組成:報頭和訊息主體。報頭由路由資訊以及有關該訊息的元資料組成。訊息主體則攜帶著應用程式的資料或有效負載。根據有效負載的型別來劃分,可以將訊息分為幾種型別,它們分別攜帶:簡單文字(TextMessage)、可序列化的物件(ObjectMessage)、屬性集合(MapMessage)、位元組流(BytesMessage)、原始值流(StreamMessage),還有無有效負載的訊息(Message)。

JMSJavaMessaging Service)是Java平臺上有關面向訊息中介軟體(MOM)的技術規範,它便於訊息系統中的Java應用程式進行訊息交換,並且通過提供標準的產生、傳送、接收訊息的介面簡化企業應用的開發,翻譯為Java訊息服務。

JMS由以下元素組成。

JMS提供者provider:連接面向訊息中介軟體的,JMS介面的一個實現。提供者可以是Java平臺的JMS實現,也可以是非Java平臺的面向訊息中介軟體的介面卡。

JMS客戶:生產或消費基於訊息的Java的應用程式或物件。

JMS生產者:建立併發送訊息的JMS客戶。

JMS消費者:接收訊息的JMS客戶。

JMS訊息:包括可以在JMS客戶之間傳遞的資料的物件

JMS佇列:一個容納那些被髮送的等待閱讀的訊息的區域。與佇列名字所暗示的意思不同,訊息的接受順序並不一定要與訊息的傳送順序相同。一旦一個訊息被閱讀,該訊息將被從佇列中移走。

JMS主題:一種支援傳送訊息給多個訂閱者的機制。

.2.3.     Java訊息服務應用程式結構支援兩種模型

1、  點對點或佇列模型

在點對點或佇列模型下,一個生產者向一個特定的佇列釋出訊息,一個消費者從該佇列中讀取訊息。這裡,生產者知道消費者的佇列,並直接將訊息傳送到消費者的佇列。

這種模式被概括為:

只有一個消費者將獲得訊息

生產者不需要在接收者消費該訊息期間處於執行狀態,接收者也同樣不需要在訊息傳送時處於執行狀態。

每一個成功處理的訊息都由接收者簽收

2、釋出者/訂閱者模型

釋出者/訂閱者模型支援向一個特定的訊息主題釋出訊息。0或多個訂閱者可能對接收來自特定訊息主題的訊息感興趣。在這種模型下,釋出者和訂閱者彼此不知道對方。這種模式好比是匿名公告板。

這種模式被概括為:

多個消費者可以獲得訊息

在釋出者和訂閱者之間存在時間依賴性。釋出者需要建立一個訂閱(subscription),以便客戶能夠訂閱。訂閱者必須保持持續的活動狀態以接收訊息,除非訂閱者建立了持久的訂閱。在那種情況下,在訂閱者未連線時釋出的訊息將在訂閱者重新連線時重新發布。

1.下載ActiveMQ

去官方網站下載:http://activemq.apache.org/

2.執行ActiveMQ

解壓縮apache-activemq-5.9.0-bin.zip,

修改配置檔案activeMQ.xml,將0.0.0.0修改為localhost

<transportConnectors>

       <transportConnector name="openwire" uri="tcp://localhost:61616"/>

       <transportConnector name="ssl"     uri="ssl://localhost:61617"/>

       <transportConnector name="stomp"   uri="stomp://localhost:61613"/>

      <transportConnector uri="http://localhost:8081"/>

       <transportConnector uri="udp://localhost:61618"/>

然後雙擊apache-activemq-5.5.1\bin\activemq.bat執行ActiveMQ程式。

3.執行程式碼

要使用Java訊息服務,你必須要有一個JMS提供者,管理會話和佇列。既有開源的提供者也有專有的提供者。

開源的提供者包括:

Apache ActiveMQ

JBoss 社群所研發的HornetQ

Joram

Coridan的MantaRay

The OpenJMS Group的OpenJMS

專有的提供者包括:

BEA的BEAWebLogic Server JMS

TIBCO Software的EMS

GigaSpaces Technologies的GigaSpaces

Softwired 2006的iBus

IONA Technologies的IONAJMS

SeeBeyond的IQManager(2005年8月被Sun Microsystems併購)

webMethods的JMS+-

my-channels的Nirvana

Sonic Software的SonicMQ

SwiftMQ的SwiftMQ

IBM的WebSphereMQ

Øjava動態代理、反射

這是獲得反射的class類物件。  反射就是知道一個字串,可以反射出它對應的類資訊(成員變數和方法),從而應用這個得到的類的物件和方法。

Class.forname(“類名”)

下面這兩種是已知類和物件,來獲取class物件。

類名.class()

物件.getClass()

通過反射的方式可以獲取class物件中的屬性、方法、建構函式等,一下是例項:

package cn.java.reflect;

import java.lang.reflect.Constructor;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.util.ArrayList;

import java.util.List;

import org.junit.Before;

import org.junit.Test;

public class MyReflect {

       public String className = null;

       @SuppressWarnings("rawtypes")

       public Class personClass = null;

       /**

        * 反射Person類

        * @throws Exception

        */

       @Before

       public void init() throws Exception {

              className = "cn.java.reflect.Person";

              personClass = Class.forName(className);

       }

       /**

        *獲取某個class檔案物件

        */

       @Test

       public void getClassName() throws Exception {

              System.out.println(personClass);

       }

       /**

        *獲取某個class檔案物件的另一種方式

        */

       @Test

       public void getClassName2() throws Exception {

              System.out.println(Person.class);

       }

       /**

        *建立一個class檔案表示的真實物件,底層會呼叫無參構造方法。

        */

       @Test

       public void getNewInstance() throws Exception {

              System.out.println(personClass.newInstance());

       }

       /**

        *獲取非私有的建構函式

        */

       @SuppressWarnings({ "rawtypes", "unchecked" })

       @Test

       public void getPublicConstructor() throws Exception {

              Constructor  constructor  = personClass.getConstructor(Long.class,String.class);

              Person person = (Person)constructor.newInstance(100L,"zhangsan");

              System.out.println(person.getId());

              System.out.println(person.getName());

       }

       /**

        *獲得私有的建構函式(暴力反射)

        */

       @SuppressWarnings({ "rawtypes", "unchecked" })

       @Test

       public void getPrivateConstructor() throws Exception {

              Constructor con = personClass.getDeclaredConstructor(String.class);

              con.setAccessible(true);//強制取消Java的許可權檢測

              Person person2 = (Person)con.newInstance("zhangsan");

              System.out.println(person2.getName());

       }

       /**

        *獲取非私有的成員變數

        */

       @SuppressWarnings({ "rawtypes", "unchecked" })

       @Test

       public void getNotPrivateField() throws Exception {

              Constructor  constructor  = personClass.getConstructor(Long.class,String.class);

              Object obj = constructor.newInstance(100L,"zhangsan");

              Field field = personClass.getField("name");

              field.set(obj, "lisi");

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

       }

       /**

        *獲取私有的成員變數

        */

       @SuppressWarnings({ "rawtypes", "unchecked" })

       @Test

       public void getPrivateField() throws Exception {

              Constructor  constructor  = personClass.getConstructor(Long.class);

              Object obj = constructor.newInstance(100L);

              Field field2 = personClass.getDeclaredField("id");

              field2.setAccessible(true);//強制取消Java的許可權檢測

              field2.set(obj,10000L);

              System.out.println(field2.get(obj));

       }

       /**

        *獲取非私有的成員函式

        */

       @SuppressWarnings({ "unchecked" })

       @Test

       public void getNotPrivateMethod() throws Exception {

              System.out.println(personClass.getMethod("toString"));

              Object obj = personClass.newInstance();//獲取空參的建構函式

              Object object = personClass.getMethod("toString").invoke(obj);

              System.out.println(object);

       }

       /**

        *獲取私有的成員函式

        */

       @SuppressWarnings("unchecked")

       @Test

       public void getPrivateMethod() throws Exception {

              Object obj = personClass.newInstance();//獲取空參的建構函式

              Method method = personClass.getDeclaredMethod("getSomeThing");

              method.setAccessible(true);

              Object value = method.invoke(obj);

              System.out.println(value);

       }

       /**

        *

        */

       @Test

       public void otherMethod() throws Exception {

              //當前載入這個class檔案的那個類載入器物件

              System.out.println(personClass.getClassLoader());

              //獲取某個類實現的所有介面

              Class[] interfaces = personClass.getInterfaces();

              for (Class class1 : interfaces) {

                     System.out.println(class1);

              }

              //反射當前這個類的直接父類

              System.out.println(personClass.getGenericSuperclass());

              /**

               * getResourceAsStream這個方法可以獲取到一個輸入流,這個輸入流會關聯到name所表示的那個檔案上。

               */

              //path 不以’/'開頭時預設是從此類所在的包下取資源,以’/'開頭則是從ClassPath根下獲取。其只是通過path構造一個絕對路徑,最終還是由ClassLoader獲取資源。

              System.out.println(personClass.getResourceAsStream("/log4j.properties"));

              //預設則是從ClassPath根下獲取,path不能以’/'開頭,最終是由ClassLoader獲取資源。

              System.out.println(personClass.getResourceAsStream("/log4j.properties"));

              //判斷當前的Class物件表示是否是陣列

              System.out.println(personClass.isArray());

              System.out.println(new String[3].getClass().isArray());

              //判斷當前的Class物件表示是否是列舉類

              System.out.println(personClass.isEnum());

              System.out.println(Class.forName("cn.java.reflect.City").isEnum());

              //判斷當前的Class物件表示是否是介面

              System.out.println(personClass.isInterface());

              System.out.println(Class.forName("cn.java.reflect.TestInterface").isInterface());

       }

}

.1.2.     動態代理(屬於反射的範疇,Proxy 用法和Class類似)

       理解為:就是在現在的java程式外面加一個代理物件,當業務線想請求service的時候,改為先向動態代理請求操作,然後動態代理先做一些事情,再調原來的service。

在之前的程式碼呼叫階段,我們用action呼叫service的方法實現業務即可。

       由於之前在service中實現的業務可能不能夠滿足當先客戶的要求,需要我們重新修改service中的方法,但是service的方法不只在我們這個模組使用,在其他模組也在呼叫,其他模組呼叫的時候,現有的service方法已經能夠滿足業務需求,所以我們不能只為了我們的業務而修改service,導致其他模組授影響。

       那怎麼辦呢?

       可以通過動態代理的方式,擴充套件我們的service中的方法實現,使得在原油的方法中增加更多的業務,而不是實際修改service中的方法,這種實現技術就叫做動態代理。

       動態代理:在不修改原業務的基礎上,基於原業務方法,進行重新的擴充套件,實現新的業務。

       例如下面的例子:

1、  舊業務

買家呼叫action,購買衣服,衣服在資料庫的標價為50元,購買流程就是簡單的呼叫。

2、  新業務

在原先的價格上可以使用優惠券,但是這個功能在以前沒有實現過,我們通過代理類,代理了原先的介面方法,在這個方法的基礎上,修改了返回值。

       代理實現流程:

1、  書寫代理類和代理方法,在代理方法中實現代理Proxy.newProxyInstance

2、  代理中需要的引數分別為:被代理的類的類載入器soneObjectclass.getClassLoader(),被代理類的所有實現介面new Class[] { Interface.class},控制代碼方法newInvocationHandler()

3、  在控制代碼方法中複寫invoke方法,invoke方法的輸入有3個引數Object proxy(代理類物件), Method method(被代理類的方法),Object[] args(被代理類方法的傳入引數),在這個方法中,我們可以定製化的開發新的業務。

4、  獲取代理類,強轉成被代理的介面

5、  最後,我們可以像沒被代理一樣,呼叫介面的認可方法,方法被呼叫後,方法名和引數列表將被傳入代理類的invoke方法中,進行新業務的邏輯流程。

              原業務介面IBoss

public interface IBoss {//介面

       int yifu(String size);

}

原業務實現類

public class Boss implements IBoss{

       public int yifu(String size){

              System.err.println("天貓小強旗艦店,老闆給客戶發快遞----衣服型號:"+size);

              //這件衣服的價錢,從資料庫讀取

              return 50;

       }

       public void kuzi(){

              System.err.println("天貓小強旗艦店,老闆給客戶發快遞----褲子");

       }

}

原業務呼叫

public class SaleAction {

              @Test

       public void saleByBossSelf() throws Exception {

              IBoss boss = new Boss();

              System.out.println("老闆自營!");

              int money = boss.yifu("xxl");

              System.out.println("衣服成交價:" + money);

       }

}

代理類

Public class ProxyBoss{

public static IBoss getProxyBoss(final int discountCoupon) throws Exception {

 # 方法引數,已經給定了優惠引數

#Proxy.newProxyInstance 就這麼用,得到一個例項物件(和原來的Boss物件用法一樣),就是getProxyBoss()的返回值。

       Object proxedObj = Proxy.newProxyInstance(Boss.class.getClassLoader(),

                     new Class[] { IBoss.class }, new InvocationHandler() {

                            public Object invoke(Object proxy, Method method,

                                          Object[] args) throws Throwable {

                                          Integer returnValue = (Integer) method.invoke(new Boss(),

                                                        args);// 呼叫原始物件以後返回的值

                                          return returnValue - discountCoupon;

                            }

                     });

       return (IBoss)proxedObj;

}

}

新業務呼叫

public class ProxySaleAction {

              @Test

       public void saleByProxy() throws Exception {

              IBoss boss = ProxyBoss.getProxyBoss(20);// 將代理的方法例項化成介面

              System.out.println("代理經營!");

              int money = boss.yifu("xxl");// 呼叫介面的方法,實際上呼叫方式沒有變

              System.out.println("衣服成交價:" + money);

       }

}

       jconsole是一種集成了上面所有命令功能的視覺化工具,可以分析jvm的記憶體使用情況和執行緒等資訊。

啟動jconsole

通過JDK/bin目錄下的“jconsole.exe”啟動Jconsole後,將自動搜尋出本機執行的所有JVM程序,不需要使用者使用jps來查詢了,雙擊其中一個程序即可開始監控。也可以“遠端連線伺服器,進行遠端虛擬機器的監控。”

概覽頁面

概述頁面顯示的是整個虛擬機器主要執行資料的概覽。

提供了和jconsole的功能類似,提供了一大堆的外掛。

外掛中,VisualGC(視覺化GC)還是比較好用的,視覺化GC可以看到記憶體的具體使用情況。

       Java虛擬機器在執行Java程式的過程中,會把它所管理的記憶體劃分為若干個不同的資料區。這些區域有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,有的區域則依賴使用者執行緒的啟動和結束而建立和銷燬,我們可以將這些區域統稱為Java執行時資料區域。

       如下圖是一個記憶體模型的關係圖(詳情見圖:記憶體劃分.png):

如上圖所示,Java虛擬機器執行時資料區域被分為五個區域:堆(Heap)、棧(Stack)、本地方法棧(NativeStack)、方法區(Method Area)、程式計數器(Program Count Register)。

.2.2.     堆(Heap)

       對於大多數應用來說,Java Heap是Java虛擬機器管理的記憶體的最大一塊,這塊區域隨著虛擬機器的啟動而建立。在實際的運用中,我們建立的物件陣列就是存放在堆裡面。如果你聽說執行緒安全的問題,就會很明確的知道JavaHeap是一塊共享的區域,操作共享區域的成員就有了鎖和同步。

       與Java Heap相關的還有Java的垃圾回收機制(GC),Java Heap是垃圾回收器管理的主要區域。程式猿所熟悉的新生代、老生代、永久代的概念就是在堆裡面,現在大多數的GC基本都採用了分代收集演算法。如果再細緻一點,Java Heap還有Eden空間,FromSurvivor空間,ToSurvivor空間等。

       JavaHeap可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。

.2.3.     棧(Stack)

       相對於Java Heap來講,Java Stack是執行緒私有的,她的生命週期與執行緒相同。Java Stack描述的是Java方法執行時的記憶體模型,每個方法執行時都會建立一個棧幀(Stack Frame)用語儲存區域性變量表運算元棧動態連結方法出口等資訊。從下圖從可以看到,每個執行緒在執行一個方法時,都意味著有一個棧幀在當前執行緒對應的棧幀中入棧和出棧。

圖中可以看到每一個棧幀中都有區域性變量表。區域性變量表存放了編譯期間的各種基本資料型別,物件引用等資訊。

       本地方法棧(Native Stack)與Java虛擬機器站(JavaStack)所發揮的作用非常相似,他們之間的區別在於虛擬機器棧為虛擬機器棧執行java方法(也就是位元組碼)服務,而本地方法棧則為使用到Native方法服務。

       方法區(Method Area)與堆(JavaHeap)一樣,是各個執行緒共享的記憶體區域,它用於儲存虛擬機器載入的類資訊常量靜態變數即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是她卻有一個別名叫做非堆(Non-Heap)。分析下Java虛擬機器規範,之所以把方法區描述為堆的一個邏輯部分,應該覺得她們都是儲存資料的角度出發的。一個儲存物件資料(堆),一個儲存靜態資訊(方法區)。

       在上文中,我們看到堆中有新生代、老生代、永久代的描述。為什麼我們將新生代、老生代、永久代三個概念一起說,那是因為HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區而已。這樣HotSpot的垃圾收集器就能想管理Java堆一樣管理這部分記憶體。簡單點說就是HotSpot虛擬機器中記憶體模型的分代,其中新生代和老生代在堆中,永久代使用方法區實現。根據官方釋出的路線圖資訊,現在也有放棄永久代並逐步採用NativeMemory來實現方法區的規劃,在JDK1.7的HotSpot中,已經把原本放在永久代的字串常量池移出。

1、  執行緒私有的資料區域有:

 Java虛擬機器棧(JavaStack)

本地方法棧(NativeStack)

2、  執行緒共有的資料區域有:

堆(JavaHeap)

方法區

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m  -XX:MaxTenuringThreshold=0

-Xmx3550m:最大堆記憶體為3550M。

-Xms3550m:初始堆記憶體為3550m。

此值可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體。

-Xmn2g:設定年輕代大小為2G。

整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。

-Xss128k:設定每個執行緒的堆疊大小。

JDK5.0以後每個執行緒堆疊大小為1M,在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程序內的執行緒數還是有限制的,不能無限生成,經驗值在 3000~5000左右。

-XX:NewRatio=4:設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5

-XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的大小比值。

設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6

-XX:MaxPermSize=16m:設定持久代大小為16m。

-XX:MaxTenuringThreshold=0:設定垃圾最大年齡。

如果設定為0的話,則年輕代物件不經過Survivor區,直 接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件 再年輕代的存活時間,增加在年輕代即被回收的概論。

收集器設定

-XX:+UseSerialGC:設定序列收集器

-XX:+UseParallelGC:設定並行收集器

-XX:+UseParalledlOldGC:設定並行年老代收集器

-XX:+UseConcMarkSweepGC:設定併發收集器

垃圾回收統計資訊

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

並行收集器設定

-XX:ParallelGCThreads=n:設定並行收集器收集時使用的CPU數。並行收集執行緒數。

-XX:MaxGCPauseMillis=n:設定並行收集最大暫停時間

-XX:GCTimeRatio=n:設定垃圾回收時間佔程式執行時間的百分比。公式為1/(1+n)

併發收集器設定

-XX:+CMSIncrementalMode:設定為增量模式。適用於單CPU情況。

-XX:ParallelGCThreads=n:設定併發收集器年輕代收集方式為並行收集時,使用的CPU數。並行收集執行緒數。

記憶體:

       Jconsole的記憶體標籤相當於視覺化的jstat命令,用於監視收集器管理的虛擬機器記憶體(java堆和永久代)的變化趨勢。

     我們通過下面的一段程式碼體驗一下它的監視功能。執行時設定的虛擬機器引數為:-Xms100m-Xmx100m -XX:+UseSerialGC,這段程式碼的作用是以64kb/50毫秒的速度往java堆記憶體中填充資料。

public class TestMemory {

       static class OOMObject {

              public byte[] placeholder = new byte[64 * 1024];

       }

       public static void fillHeap(int num) throws Exception {

              ArrayList<OOMObject> list = new ArrayList<OOMObject>();

              for (int i = 0; i < num; i++) {

                     Thread.sleep(50);

                     list.add(new OOMObject());

              }

              System.gc();

       }

       public static void main(String[] args) throws Exception {

              fillHeap(1000);

              Thread.sleep(500000);

       }

}

從圖中可以看出,執行軌跡成曲線增長,迴圈1000次後,雖然整個新生代Eden和Survivor區都基本上被清空了,但是老年代仍然保持峰值狀態,這說明,填充的資料在GC後仍然存活,因為list的作用域沒有結束。如果把System.gc();移到fillHeap(1000);後,就可以全部回收掉。

執行緒:

jconsole執行緒標籤相當於可視化了jstack命令,遇到執行緒停頓時,可以使用這個也籤進行監控分析。執行緒長時間停頓的