Ø java多線程增強
1. java多線程基本知識
1.1進程介紹
不管是我們開發的應用程序,還是我們運行的其他的應用程序,都需要先把程序安裝在本地的硬盤上。然後找到這個程序的啓動文件,啓動程序的時候,其實是電腦把當前的這個程序加載到內存中,在內存中需要給當前的程序分配一段獨立的運行空間。這片空間就專門負責當前這個程序的運行。
不同的應用程序運行的過程中都需要在內存中分配自己獨立的運行空間,彼此之間不會相互的影響。我們把每個獨立應用程序在內存的獨立空間稱爲當前應用程序運行的一個進程。
進程:它是內存中的一段獨立的空間,可以負責當前應用程序的運行。當前這個進程負責調度當前程序中的所有運行細節。
1.2 線程介紹
啓動的QQ聊天軟件,需要和多個人進行聊天。這時多個人之間是不能相互影響,但是它們都位於當前QQ這個軟件運行時所分配的內容的獨立空間中。
在一個進程中,每個獨立的功能都需要獨立的去運行,這時又需要把當前這個進程劃分成多個運行區域,每個獨立的小區域(小單元)稱爲一個線程。
線程:它是位於進程中,負責當前進程中的某個具備獨立運行資格的空間。
進程是負責整個程序的運行,而線程是程序中具體的某個獨立功能的運行。一個進程中至少應該有一個線程。
1.3多線程介紹
現在的操作系統基本都是多用戶,多任務的操作系統。每個任務就是一個進程。而在這個進程中就會有線程。
真正可以完成程序運行和功能的實現靠的是進程中的線程。
多線程:在一個進程中,我們同時開啓多個線程,讓多個線程同時去完成某些任務(功能)。
多線程的目的:提高程序的運行效率。
1.4 多線程運行的原理
cpu在線程中做時間片的切換。
其實真正電腦中的程序的運行不是同時在運行的。CPU負責程序的運行,而CPU在運行程序的過程中某個時刻點上,它其實只能運行一個程序。而不是多個程序。而CPU它可以在多個程序之間進行高速的切換。而切換頻率和速度太快,導致人的肉看看不到。
每個程序就是進程, 而每個進程中會有多個線程,而CPU是在這些線程之間進行切換。
瞭解了CPU對一個任務的執行過程,我們就必須知道,多線程可以提高程序的運行效率,但不能無限制的開線程。
1.5實現線程的兩種方式
1、繼承Thread的原理
import java.util.Random; public class MyThreadWithExtends extends Thread { String flag; public MyThreadWithExtends(String flag){ this.flag = flag; } @Override public void run() { // 獲取當前線程的線程名 String tname = Thread.currentThread().getName(); System.out.println(tname+"線程的run方法被調用……"); Random random = new Random(); for(int i=0;i<20;i++){ try { Thread.sleep(random.nextInt(10)*100); System.out.println(tname+ "...."+ flag); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread thread1 = new MyThreadWithExtends("a"); Thread thread2 = new MyThreadWithExtends("b"); thread1.start(); thread2.start(); /** * 如果是調用thread的run方法,則只是一個普通的方法調用,不會開啓新的線程 */ // thread1.run(); // thread2.run(); } }
2、聲明實現 Runnable 接口的類
public class MyThreadWithImpliment implements Runnable { int x; public MyThreadWithImpliment(int x) { this.x = x; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("線程" + name + "的run方法被調用……"); for (int i = 0; i < 10; i++) { System.out.println(x); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread thread1 = new Thread(new MyThreadWithImpliment(1), "thread-1"); Thread thread2 = new Thread(new MyThreadWithImpliment(2), "thread-2"); thread1.start(); thread2.start(); // 注意調用run和調用start的區別,直接調用run,則都運行在main線程中 // thread1.run(); // thread2.run(); }
3、還可以實現Callable接口
2. java同步關鍵詞解釋
2.1 synchronized
synchronized是用來實現線程同步的!!!
加同步格式:
synchronized( 需要一個任意的對象(鎖) ){
代碼塊中放操作共享數據的代碼。
}
package cn.itcast_01_mythread.thread.testThread; public class MySynchronized { public static void main(String[] args) { final MySynchronized mySynchronized = new MySynchronized(); final MySynchronized mySynchronized2 = new MySynchronized(); new Thread("thread1") { public void run() { synchronized ("sb") { try { System.out.println(this.getName()+" start"); // int i =1/0; //如果發生異常,jvm會將鎖釋放 Thread.sleep(5000); System.out.println(this.getName()+"醒了"); System.out.println(this.getName()+" end"); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread("thread2") { public void run() { synchronized ("sb") { //爭搶同一把鎖時,線程1沒釋放之前,線程2只能等待 // synchronized (mySynchronized2) { //如果不是一把鎖,可以看到兩句話同時打印 System.out.println(this.getName()+" start"); System.out.println(this.getName()+" end"); } } }.start(); } }
Ø synchronized的缺陷
synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性。
如果一個代碼塊被synchronized修飾了,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖,而這裏獲取鎖的線程釋放鎖只會有兩種情況:
1)獲取鎖的線程執行完了該代碼塊,然後線程釋放對鎖的佔有;
2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。
例子1:
如果這個獲取鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便只能乾巴巴地等待,試想一下,這多麼影響程序執行效率。
因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。
例子2:
當有多個線程讀寫文件時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。
但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:
如果多個線程都只是進行讀操作,當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作。
因此就需要一種機制來使得多個線程都只是進行讀操作時,線程之間不會發生衝突,通過Lock就可以辦到。
另外,通過Lock可以知道線程有沒有成功獲取到鎖。這個是synchronized無法辦到的。
總的來說,也就是說Lock提供了比synchronized更多的功能。
2.2 lock
Ø lock和synchronized的區別
1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;
2)Lock和synchronized有一點非常大的不同,採用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的佔用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
3)線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定, 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷,如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情。
Ø 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(long time, TimeUnit unit)、lockInterruptibly()是用來獲取鎖的。 unLock()方法是用來釋放鎖的。
四個獲取鎖方法的區別:
lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
tryLock(long time, 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()的正確使用方法
import java.util.ArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyLockTest { private static ArrayList<Integer> arrayList = new ArrayList<Integer>(); static Lock lock = new ReentrantLock(); // 注意這個地方 public static <E> void main(String[] args) { new Thread() { public void run() { Thread thread = Thread.currentThread(); lock.lock(); // 阻塞式的 try { System.out.println(thread.getName() + "得到了鎖"); Thread.sleep(10000); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "釋放了鎖"); lock.unlock(); } }; }.start(); new Thread() { public void run() { Thread thread = Thread.currentThread(); lock.lock(); try { System.out.println(thread.getName() + "得到了鎖"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "釋放了鎖"); lock.unlock(); } }; }.start(); } }
例子2,tryLock()的使用方法
/** * 觀察現象:一個線程獲得鎖後,另一個線程取不到鎖,不會一直等待 * @author * */ public class MyTryLock { private static ArrayList<Integer> arrayList = new ArrayList<Integer>(); static Lock lock = new ReentrantLock(); // 注意這個地方 public static void main(String[] args) { new Thread() { public void run() { Thread thread = Thread.currentThread(); boolean tryLock = lock.tryLock(); // 非阻塞式的 System.out.println(thread.getName()+" "+tryLock); if (tryLock) { try { System.out.println(thread.getName() + "得到了鎖"); Thread.sleep(1000); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "釋放了鎖"); lock.unlock(); } } }; }.start(); new Thread() { public void run() { Thread thread = Thread.currentThread(); boolean tryLock = lock.tryLock(); System.out.println(thread.getName()+" "+tryLock); if (tryLock) { try { System.out.println(thread.getName() + "得到了鎖"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "釋放了鎖"); lock.unlock(); } } }; }.start(); } }
例子3,lockInterruptibly()響應中斷的使用方法:
package cn.itcast_01_mythread.thread.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 觀察現象:如果thread-0得到了鎖,阻塞。。。 * thread-1嘗試獲取鎖,如果拿不到,會等待,但是可以被中斷等待 * @author * */ public class MyInterruptibly { private Lock lock = new ReentrantLock(); public static void main(String[] args) { MyInterruptibly test = new MyInterruptibly(); MyThread thread0 = new MyThread(test); MyThread thread1 = new MyThread(test); thread0.start(); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); //調用中斷方法來測試能否中斷等待中的線程 System.out.println("====================="); } public void insert(Thread thread) throws InterruptedException{ lock.lockInterruptibly(); //注意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然後將InterruptedException拋出 try { System.out.println(thread.getName()+"得到了鎖"); long startTime = System.currentTimeMillis(); for( ; ;) { if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插入數據 } } finally { System.out.println(Thread.currentThread().getName()+"執行finally"); lock.unlock(); System.out.println(thread.getName()+"釋放了鎖"); } } } class MyThread extends Thread { private MyInterruptibly test = null; public MyThread(MyInterruptibly test) { this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+"被中斷"); } } }
² ReadWriteLock (可以區別對待讀、寫的操作)
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達到的效果
/** * 一個線程又要讀又要寫,用synchronize來實現的話,讀寫操作都只能鎖住後一個線程一個線程地進行 * * @author * */ public class MySynchronizedReadWrite { public static void main(String[] args) { final MySynchronizedReadWrite test = new MySynchronizedReadWrite(); new Thread() { public void run() { test.operate(Thread.currentThread()); }; }.start(); new Thread() { public void run() { test.operate(Thread.currentThread()); }; }.start(); } public synchronized void operate(Thread thread) { long start = System.currentTimeMillis(); int i = 0; while (System.currentTimeMillis() - start <= 1) { i++; if (i % 4 == 0) { System.out.println(thread.getName() + "正在進行寫操作"); } else { System.out.println(thread.getName() + "正在進行讀操作"); } } System.out.println(thread.getName() + "讀寫操作完畢"); } }
運行結果:
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行讀操作
Thread-0正在進行寫操作
Thread-0讀寫操作完畢
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行寫操作
例子2:改成用讀寫鎖的話:
/** * 使用讀寫鎖,可以實現讀寫分離鎖定,讀操作併發進行,寫操作鎖定單個線程 * * 如果有一個線程已經佔用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。 * 如果有一個線程已經佔用讀鎖,其他線程還是申請讀鎖,則可以併發進行 * 如果有一個線程已經佔用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。 * @author * */ public class MyReentrantReadWriteLock { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final MyReentrantReadWriteLock test = new MyReentrantReadWriteLock(); new Thread(){ public void run() { test.get(Thread.currentThread()); test.write(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.get(Thread.currentThread()); test.write(Thread.currentThread()); }; }.start(); } /** * 讀操作,用讀鎖來鎖定 * @param thread */ public void get(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1000) { System.out.println(thread.getName()+"正在進行讀操作"); Thread.sleep(100); } System.out.println(thread.getName()+"讀操作完畢"); }catch(Exception e){} finally { rwl.readLock().unlock(); } } /** * 寫操作,用寫鎖來鎖定 * @param thread */ public void write(Thread thread) { rwl.writeLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在進行寫操作"); } System.out.println(thread.getName()+"寫操作完畢"); } finally { rwl.writeLock().unlock(); } } }
運行結果:併發讀,單線程寫
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-1正在進行讀操作
Thread-1正在進行讀操作
Thread-0正在進行讀操作
Thread-0讀操作完畢
Thread-1讀操作完畢
Thread-1正在進行寫操作
Thread-1正在進行寫操作
Thread-1正在進行寫操作
Thread-1正在進行寫操作
Thread-0正在進行寫操作
Thread-0正在進行寫操作
Thread-0正在進行寫操作
Thread-0正在進行寫操作
Thread-0寫操作完畢
注意:
不過要注意的是,如果有一個線程已經佔用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。
如果有一個線程已經佔用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
² Lock和synchronized的選擇
1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;
2)synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;
4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
5)Lock可以提高多個線程進行讀操作的效率。
在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。
3.java併發包
3.1 java併發包介紹
JDK5.0 以後的版本都引入了高級併發特性,大多數的特性在java.util.concurrent 包中,是專門用於多線程發編程的,充分利用了現代多處理器和多核心繫統的功能以編寫大規模併發應用程序。主要包含原子量、併發集合、同步器、可重入鎖,並對線程池的構造提供
了強力的支持。
線程池
² 線程池的5種創建方式:
1、 Single Thread Executor : 只有一個線程的線程池,因此所有提交的任務是順序執行,
代碼: Executors.newSingleThreadExecutor()
2、 Cached Thread Pool : 線程池裏有很多線程需要同時執行,老的可用線程將被新的任務觸發重新執行,如果線程超過60秒內沒執行,那麼將被終止並從池中刪除,
代碼:Executors.newCachedThreadPool()
3、 Fixed Thread Pool : 擁有固定線程數的線程池,如果沒有任務執行,那麼線程會一直等待,
代碼: Executors.newFixedThreadPool(4)
在構造函數中的參數4是線程池的大小,你可以隨意設置,也可以和cpu的核數量保持一致,獲取cpu的數量int cpuNums = Runtime.getRuntime().availableProcessors();
4、 Scheduled Thread Pool : 用來調度即將執行的任務的線程池,
代碼:Executors.newScheduledThreadPool()
5、 Single Thread Scheduled Pool : 只有一個線程,用來調度執行將來的任務,代碼:Executors.newSingleThreadScheduledExecutor()
² 線程池的使用
所謂給線程池提交任務,就是:
1、你將任務(業務處理邏輯)寫到一個runnable或者callable的執行方法<run() | call()>
2、將這個runnable對象提交給線程池即可
提交 Runnable ,任務完成後 Future 對象返回 null
見代碼:
public class ThreadPoolWithRunable { /** * 通過線程池執行線程 * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { //創建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); for(int i = 1; i < 50; i++){ pool.execute(new Runnable() { @Override public void run() { System.out.println("thread name: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } Thread.sleep(10000); for(int i = 1; i < 50; i++){ pool.execute(new Runnable() { @Override public void run() { System.out.println("thread name: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } pool.shutdown(); } }
提交 Callable,該方法返回一個 Future 實例表示任務的狀態
見代碼:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * callable 跟runnable的區別: * runnable的run方法不會有任何返回結果,所以主線程無法獲得任務線程的返回值 * * callable的call方法可以返回結果,但是主線程在獲取時是被阻塞,需要等待任務線程返回才能拿到結果 * @author * */ public class ThreadPoolWithcallable { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newFixedThreadPool(4); for(int i = 0; i < 10; i++){ Future<String> submit = pool.submit(new Callable<String>(){ @Override public String call() throws Exception { //System.out.println("a"); System.out.println(Thread.currentThread().getName()+ "-->正在工作"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+ "-->工作結束"); return "b--"+Thread.currentThread().getName(); } }); //從Future中get結果,這個方法是會被阻塞的,一直要等到線程任務返回結果 System.out.println("獲取到結果:" + submit.get()); } pool.shutdown(); } }
4.java JMS技術
4.1 什麼是JMS
JMS即Java消息服務(Java Message Service)應用程序接口是一個Java平臺中關於面向消息中間件(MOM)的API,用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。Java消息服務是一個與具體平臺無關的API,絕大多數MOM提供商都對JMS提供支持。
JMS是一種與廠商無關的 API,用來訪問消息收發系統消息。它類似於JDBC(Java Database Connectivity):這裏,JDBC 是可以用來訪問許多不同關係數據庫的 API,而 JMS 則提供同樣與廠商無關的訪問方法,以訪問消息收發服務。許多廠商都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ,這只是幾個例子。 JMS 使您能夠通過消息收發服務(有時稱爲消息中介程序或路由器)從一個 JMS 客戶機向另一個 JMS客戶機發送消息。消息是 JMS 中的一種類型對象,由兩部分組成:報頭和消息主體。報頭由路由信息以及有關該消息的元數據組成。消息主體則攜帶着應用程序的數據或有效負載。根據有效負載的類型來劃分,可以將消息分爲幾種類型,它們分別攜帶:簡單文本(TextMessage)、可序列化的對象 (ObjectMessage)、屬性集合 (MapMessage)、字節流 (BytesMessage)、原始值流 (StreamMessage),還有無有效負載的消息 (Message)。
5. JMS規範
5.1 專業技術規範
JMS(Java Messaging Service)是Java平臺上有關面向消息中間件(MOM)的技術規範,它便於消息系統中的Java應用程序進行消息交換,並且通過提供標準的產生、發送、接收消息的接口簡化企業應用的開發,翻譯爲Java消息服務。
5.2 體系架構
JMS由以下元素組成。
JMS提供者:連接面向消息中間件的,JMS接口的一個實現。提供者可以是Java平臺的JMS實現,也可以是非Java平臺的面向消息中間件的適配器。
JMS客戶:生產或消費基於消息的Java的應用程序或對象。
JMS生產者:創建併發送消息的JMS客戶。
JMS消費者:接收消息的JMS客戶。
JMS消息:包括可以在JMS客戶之間傳遞的數據的對象
JMS隊列:一個容納那些被髮送的等待閱讀的消息的區域。一旦一個消息被閱讀,該消息將被從隊列中移走。
JMS主題:一種支持發送消息給多個訂閱者的機制。
5.3 Java消息服務應用程序結構支持兩種模型
1、 點對點或隊列模型
在點對點或隊列模型下,一個生產者向一個特定的隊列發佈消息,一個消費者從該隊列中讀取消息。這裏,生產者知道消費者的隊列,並直接將消息發送到消費者的隊列。
這種模式被概括爲:
只有一個消費者將獲得消息
生產者不需要在接收者消費該消息期間處於運行狀態,接收者也同樣不需要在消息發送時處於運行狀態。
每一個成功處理的消息都由接收者簽收
2、發佈者/訂閱者模型
發佈者/訂閱者模型支持向一個特定的消息主題發佈消息。0或多個訂閱者可能對接收來自特定消息主題的消息感興趣。在這種模型下,發佈者和訂閱者彼此不知道對方。這種模式好比是匿名公告板。
這種模式被概括爲:
多個消費者可以獲得消息
在發佈者和訂閱者之間存在時間依賴性。發佈者需要建立一個訂閱(subscription),以便客戶能夠訂閱。訂閱者必須保持持續的活動狀態以接收消息,除非訂閱者建立了持久的訂閱。在那種情況下,在訂閱者未連接時發佈的消息將在訂閱者重新連接時重新發布。
5.4 常用的JMS實現
要使用Java消息服務,你必須要有一個JMS提供者,管理會話和隊列。既有開源的提供者也有專有的提供者。
ü 開源的提供者包括:
Apache ActiveMQ
JBoss 社區所研發的 HornetQ
Joram
Coridan的MantaRay
The OpenJMS Group的OpenJMS
ü 專有的提供者包括:
BEA的BEA WebLogic Server JMS
TIBCO Software的EMS
GigaSpaces Technologies的GigaSpaces
Softwired 2006的iBus
IONA Technologies的IONA JMS
SeeBeyond的IQManager(2005年8月被Sun Microsystems併購)
webMethods的JMS+ -
my-channels的Nirvana
Sonic Software的SonicMQ
SwiftMQ的SwiftMQ
IBM的WebSphere MQ
5.5 代碼演示
注:新版直接解壓即可運行
1.下載ActiveMQ
去官方網站下載:http://activemq.apache.org/
2.運行ActiveMQ
解壓縮apache-activemq-5.5.1-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\win64\activemq.bat運行ActiveMQ程序。
啓動ActiveMQ以後,登陸:http://localhost:8161/admin/,創建一個Queue,命名爲FirstQueue。
3.運行代碼
package cn.itcast_03_mq.queue
package cn.itcast_03_mq.topic
6.java反射、動態代理、sockect通信
6.1反射
通過反射的方式可以獲取class對象中的屬性、方法、構造函數等,一下是實例:
public class MyReflect { public String className = null; @SuppressWarnings("rawtypes") // Class 代表JVM中加載好的一個特定class文件 public Class personClass = null; /** * 反射Person類 * @throws Exception */ @Before public void init() throws Exception { className = "cn.itcast_04_reflect.Person"; // 通過Class.forName( 類全路徑字符串),就能將這個類的class文件加載到JVM內存中 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();//獲取空參的構造函數 Method toStringMethod = personClass.getMethod("toString"); Object object = toStringMethod.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")); 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.itcast_04_reflect.City").isEnum()); //判斷當前的Class對象表示是否是接口 System.out.println(personClass.isInterface()); System.out.println(Class.forName("cn.itcast_04_reflect.TestInterface").isInterface()); } }
6.2 動態代理
在之前的代碼調用階段,我們用action調用service的方法實現業務即可。
由於之前在service中實現的業務可能不能夠滿足當先客戶的要求,需要我們重新修改service中的方法,但是service的方法不只在我們這個模塊使用,在其他模塊也在調用,其他模塊調用的時候,現有的service方法已經能夠滿足業務需求,所以我們不能只爲了我們的業務而修改service,導致其他模塊授影響。
那怎麼辦呢?
可以通過動態代理的方式,擴展我們的service中的方法實現,使得在原有的方法中增加更多的業務,而不是實際修改service中的方法,這種實現技術就叫做動態代理。
動態代理:在不修改原業務的基礎上,基於原業務方法,進行重新的擴展,實現新的業務。
例如下面的例子:
1、 舊業務
買家調用action,購買衣服,衣服在數據庫的標價爲50元,購買流程就是簡單的調用。
2、 新業務
在原先的價格上可以使用優惠券,但是這個功能在以前沒有實現過,我們通過代理類,代理了原先的接口方法,在這個方法的基礎上,修改了返回值。
代理實現流程:
1、 書寫代理類和代理方法,在代理方法中實現代理Proxy.newProxyInstance
2、 代理中需要的參數分別爲:被代理的類的類加載器soneObjectclass.getClassLoader(),被代理類的所有實現接口new Class[] { Interface.class },句柄方法new InvocationHandler()
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 static IBoss getProxyBoss(final int discountCoupon) throws Exception { 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); } } |
源代碼:https://github.com/JiyangM/Java_basic