Java线程新特征——Java并发库
一、線程池
??Sun在Java5中,對Java線程的類庫做了大量的擴展,其中線程池就是Java5的新特征之一,除了線程池之外,還有很多多線程相關的內容,為多線程的編程帶來了極大便利。為了編寫高效穩(wěn)定可靠的多線程程序,線程部分的新增內容顯得尤為重要。 ????有關Java5線程新特征的內容全部在java.util.concurrent下面,里面包含數(shù)目眾多的接口和類,熟悉這部分API特征是一項艱難的學習過程。當然新特征對做多線程程序沒有必須的關系,在java5之前通用可以寫出很優(yōu)秀的多線程程序。只是代價不一樣而已。 線程池的基本思想還是一種對象池的思想,開辟一塊內存空間,里面存放了眾多(未死亡)的線程,池中線程執(zhí)行調度由池管理器來處理。當有線程任務時,從池中取一個,執(zhí)行完成后線程對象歸池,這樣可以避免反復創(chuàng)建線程對象所帶來的性能開銷,節(jié)省了系統(tǒng)的資源。 ????在Java5之前,要實現(xiàn)一個線程池是相當有難度的,現(xiàn)在Java5為我們做好了一切,我們只需要按照提供的API來使用,即可享受線程池帶來的極大便利。 Java5的線程池分好多種:固定尺寸的線程池、可變尺寸連接池。 ? 在使用線程池之前,必須知道如何去創(chuàng)建一個線程池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量創(chuàng)建連接池的靜態(tài)方法,是必須掌握的。a、固定大小的線程池
import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; /** * Java線程:線程池- * * */ public class Test { public static void main(String[] args) { //創(chuàng)建一個可重用固定線程數(shù)的線程池 ExecutorService pool = Executors.newFixedThreadPool(2); //創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當然也實現(xiàn)了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //將線程放入池中進行執(zhí)行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); //關閉線程池 pool.shutdown(); } } class MyThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"正在執(zhí)行..."); } }運行結果:
pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-2正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-2正在執(zhí)行...?如果將線程池的大小改為4,則運行結果如下:
pool-1-thread-2正在執(zhí)行... pool-1-thread-3正在執(zhí)行... pool-1-thread-3正在執(zhí)行... pool-1-thread-2正在執(zhí)行... pool-1-thread-1正在執(zhí)行...?
b、單任務線程池
在上例的基礎上改一行創(chuàng)建pool對象的代碼為:
//創(chuàng)建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。 ExecutorService pool = Executors.newSingleThreadExecutor();則,運行結果為:
pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... 對于以上兩種連接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池需要排隊等待。 一旦池中有線程完畢,則排隊等待的某個線程會入池執(zhí)行。?c、可變尺寸的線程池
?與上面的類似,只是改動下pool的創(chuàng)建方式:
//創(chuàng)建一個可根據需要創(chuàng)建新線程的線程池,但是在以前構造的線程可用時將重用它們。 ExecutorService pool = Executors.newCachedThreadPool();運行結果如下:
pool-1-thread-1正在執(zhí)行... pool-1-thread-5正在執(zhí)行... pool-1-thread-4正在執(zhí)行... pool-1-thread-3正在執(zhí)行... pool-1-thread-2正在執(zhí)行...d、延遲連接池
package concurrent; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Java線程:線程池- * * */ public class Test { public static void main(String[] args) { //創(chuàng)建一個線程池,它可那排在給定延遲后運行命令或者定期地執(zhí)行 ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); //創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當然也實現(xiàn)了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //將線程放入池中進行執(zhí)行 pool.execute(t1); pool.execute(t2); pool.execute(t3); //使用延遲執(zhí)行風格的方法 pool.schedule(t4, 5, TimeUnit.SECONDS); pool.schedule(t5, 10, TimeUnit.SECONDS); //關閉線程池 pool.shutdown(); } } class MyThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"正在執(zhí)行..."); } }e、單任務連接線程池
在d的代碼基礎上,做改動
//創(chuàng)建一個單線程執(zhí)行程序,它可安排在給定延遲后運行命令或者定期地執(zhí)行。 ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();運行時,會發(fā)現(xiàn),t4延遲5s后得到執(zhí)行,t5延遲10s后得到執(zhí)行。運行結果如下:
pool-1-thread-2正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-1正在執(zhí)行... pool-1-thread-2正在執(zhí)行... pool-1-thread-1正在執(zhí)行...f、自定義線程池
package concurrent; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Java線程:線程池-自定義線程池 * * */ public class Test { public static void main(String[] args) { //創(chuàng)建等待隊列 BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20); //創(chuàng)建一個單線程執(zhí)行程序,它可安排在給定延遲后運行命令或者定期地執(zhí)行。 ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue); //創(chuàng)建實現(xiàn)了Runnable接口對象,Thread對象當然也實現(xiàn)了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); Thread t6 = new MyThread(); Thread t7 = new MyThread(); //將線程放入池中進行執(zhí)行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); pool.execute(t7); //關閉線程池 pool.shutdown(); } } class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在執(zhí)行..."); try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } } }運行結構如下:
創(chuàng)建自定義線程池的構造方法很多,本例中參數(shù)的含義如下:
ThreadPoolExecutor
public ThreadPoolExecutor(int?corePoolSize,int?maximumPoolSize,long?keepAliveTime,TimeUnit?unit,BlockingQueue<Runnable>?workQueue)二、有返回值的線程
在Java5之前,線程是沒有返回值的,常常為了“有”返回值,破費周折,而且代碼很不好寫?;蛘吒纱嗬@過這道坎,走別的路了?,F(xiàn)在Java終于有可返回值的線程了。 ?可返回值的任務必須實現(xiàn)Callable接口,類似的,無返回值的任務必須實現(xiàn)Runnable接口。 ?執(zhí)行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。?下面是一個簡單的例子:
?
package MultiThread; import java.util.concurrent.*; /** * Java線程:有返回值的線程 * * */ public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //創(chuàng)建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(2); //創(chuàng)建兩個有返回值的任務 Callable<String> c1 = new MyCallable("A"); Callable<String> c2 = new MyCallable("B"); //執(zhí)行任務并獲取Future對象 Future<String> f1 = pool.submit(c1); Future<String> f2 = pool.submit(c2); //從Future對象上獲取任務的返回值,并輸出到控制臺 System.out.println(">>>"+f1.get().toString()); System.out.println(">>>"+f2.get().toString()); //關閉線程池 pool.shutdown(); } } class MyCallable implements Callable<String>{ private String oid; MyCallable(String oid) { this.oid = oid; } @Override public String call() throws Exception { return oid+"任務返回的內容"; } }運行結果:
>>>A任務返回的內容 >>>B任務返回的內容比較簡單,要深入了解還需要看Callable和Future接口的API啊。
三、并發(fā)庫的鎖
在Java5中,專門提供了鎖對象,利用鎖可以方便的實現(xiàn)資源的封鎖,用來控制對競爭資源并發(fā)訪問的控制,這些內容主要集中在java.util.concurrent.locks 包下面,里面有三個重要的接口Condition、Lock、ReadWriteLock。
| Condition | Condition 將 Object 監(jiān)視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現(xiàn)組合使用,為每個對象提供多個等待 set(wait-set)。 |
| Lock | Lock 實現(xiàn)提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。 |
| ReadWriteLock | ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作。 |
?
?
?
?
?有關鎖的介紹,API文檔解說很多,看得很煩,還是看個例子再看文檔比較容易理解
?a、普通鎖
package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Java線程:鎖 * * */ public class Test { public static void main(String[] args) { //創(chuàng)建并發(fā)訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創(chuàng)建一個鎖對象 Lock lock = new ReentrantLock(); //創(chuàng)建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); //創(chuàng)建一些并發(fā)訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊 UserThread ut1 = new UserThread("取款線程1", myCount, -4000, lock); UserThread ut2 = new UserThread("存款線程1", myCount, 6000, lock); UserThread ut3 = new UserThread("取款線程2", myCount, -8000, lock); UserThread ut4 = new UserThread("存款線程2", myCount, 800, lock); //在線程池中執(zhí)行各個用戶的操作 pool.execute(ut1); pool.execute(ut2); pool.execute(ut3); pool.execute(ut4); //關閉線程池 pool.shutdown(); } } /** * 信用卡的用戶 線程 * 多個用戶線程操作該信用卡 */ class UserThread implements Runnable { private String threadName; //用戶線程 private MyCount myCount; //所要操作的賬戶 private int iocash; //操作的金額,當然有正負之分了 private Lock myLock; //執(zhí)行操作所需的鎖對象 UserThread(String name, MyCount myCount, int iocash, Lock myLock) { this.threadName = name; this.myCount = myCount; this.iocash = iocash; this.myLock = myLock; } public void run() { //獲取鎖 myLock.lock(); //執(zhí)行現(xiàn)金業(yè)務 System.out.println(threadName + "正在操作" + myCount + "賬戶,操作金額為" + iocash + ",當前金額為" + myCount.getCash()); myCount.setCash(myCount.getCash() + iocash); System.out.println("\t操作成功,操作金額為" + iocash + ",當前金額為" + myCount.getCash()); //釋放鎖,否則別的線程沒有機會執(zhí)行了 myLock.unlock(); } } /** * 信用卡賬戶,可隨意透支 */ class MyCount { private String oid; //賬號 private int cash; //賬戶余額 MyCount(String oid, int cash) { this.oid = oid; this.cash = cash; } public String getOid() { return oid; } public void setOid(String oid) { this.oid = oid; } public int getCash() { return cash; } public void setCash(int cash) { this.cash = cash; } @Override public String toString() { return "MyCount{" + "oid='" + oid + '\'' + ", cash=" + cash + '}'; } }運行結果:
取款線程1正在操作MyCount{oid='95599200901215522', cash=10000}賬戶,操作金額為-4000,當前金額為10000 操作成功,操作金額為-4000,當前金額為6000 存款線程1正在操作MyCount{oid='95599200901215522', cash=6000}賬戶,操作金額為6000,當前金額為6000 操作成功,操作金額為6000,當前金額為12000 存款線程2正在操作MyCount{oid='95599200901215522', cash=12000}賬戶,操作金額為800,當前金額為12000 操作成功,操作金額為800,當前金額為12800 取款線程2正在操作MyCount{oid='95599200901215522', cash=12800}賬戶,操作金額為-8000,當前金額為12800 操作成功,操作金額為-8000,當前金額為4800 從上面的輸出可以看到,利用鎖對象太方便了,比直接在某個不知情的對象上用鎖清晰多了。但一定要注意的是,在獲取了鎖對象后,用完后應該盡快釋放鎖,以便別的等待該鎖的線程有機會去執(zhí)行。b、讀寫鎖
?在a中提到了Lock接口以及對象,使用它可以很優(yōu)雅的控制了競爭資源的安全訪問,但是這種鎖不區(qū)分讀寫,稱這種鎖為普通鎖。為了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在一定程度上提高了程序的執(zhí)行效率。 Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現(xiàn)ReentrantReadWriteLock,詳細的API可以查看JavaAPI文檔。 下面這個例子是在文例子的基礎上,將普通鎖改為讀寫鎖,并添加賬戶余額查詢的功能,代碼如下: package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Java線程:鎖 * * */ public class Test { public static void main(String[] args) { //創(chuàng)建并發(fā)訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創(chuàng)建一個鎖對象 ReadWriteLock lock = new ReentrantReadWriteLock(); //創(chuàng)建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); //創(chuàng)建一些并發(fā)訪問用戶線程,一個信用卡,存的存,取的取,好熱鬧啊 UserThread ut1 = new UserThread("取款線程1", myCount, -4000, lock,false); UserThread ut2 = new UserThread("存款線程1", myCount, 6000, lock,false); UserThread ut3 = new UserThread("取款線程2", myCount, -8000, lock,false); UserThread ut4 = new UserThread("存款線程2", myCount, 800, lock,false); UserThread ut5 = new UserThread("查詢", myCount, 0, lock,true); //在線程池中執(zhí)行各個用戶的操作 pool.execute(ut1); pool.execute(ut2); pool.execute(ut3); pool.execute(ut4); pool.execute(ut5); //關閉線程池 pool.shutdown(); } } /** * 信用卡的用戶 線程 * 多個用戶線程操作該信用卡 */ class UserThread implements Runnable { private String threadName; //用戶線程 private MyCount myCount; //所要操作的賬戶 private int iocash; //操作的金額,當然有正負之分了 private ReadWriteLock myLock; //執(zhí)行操作所需的鎖對象 private boolean ischeck; //是否查詢 UserThread(String name, MyCount myCount, int iocash, ReadWriteLock myLock,boolean ischeck) { this.threadName = name; this.myCount = myCount; this.iocash = iocash; this.myLock = myLock; this.ischeck=ischeck; } public void run() { if(ischeck){ //獲取讀鎖 myLock.readLock().lock(); //執(zhí)行查詢 System.out.println("讀:"+threadName + "正在查詢" + myCount + "賬戶,,當前金額為" + myCount.getCash()); //釋放獲取到的讀鎖 myLock.readLock().unlock(); }else{ //獲取寫鎖 myLock.writeLock().lock(); myCount.setCash(myCount.getCash() + iocash); System.out.println("寫:"+threadName+"操作成功,操作金額為" + iocash + ",當前金額為" + myCount.getCash()); //釋放鎖獲取到的寫鎖 myLock.writeLock().unlock(); } } } /** * 信用卡賬戶,可隨意透支 */ class MyCount { private String oid; //賬號 private int cash; //賬戶余額 MyCount(String oid, int cash) { this.oid = oid; this.cash = cash; } public String getOid() { return oid; } public void setOid(String oid) { this.oid = oid; } public int getCash() { return cash; } public void setCash(int cash) { this.cash = cash; } @Override public String toString() { return "MyCount{" + "oid='" + oid + '\'' + ", cash=" + cash + '}'; } }?運行結果:
寫:取款線程1操作成功,操作金額為-4000,當前金額為6000 寫:取款線程2操作成功,操作金額為-8000,當前金額為-2000 寫:存款線程1操作成功,操作金額為6000,當前金額為4000 讀:查詢正在查詢MyCount{oid='95599200901215522', cash=4000}賬戶,,當前金額為4000 寫:存款線程2操作成功,操作金額為800,當前金額為4800在實際開發(fā)中,最好在能用讀寫鎖的情況下使用讀寫鎖,而不要用普通鎖,以求更好的性能。
?四、信號量
Java的信號量實際上是一個功能完畢的計數(shù)器,對控制一定資源的消費與回收有著很重要的意義,信號量常常用于多線程的代碼中,并能監(jiān)控有多少數(shù)目的線程等待獲取資源,并且通過信號量可以得知可用資源的數(shù)目等等,這里總是在強調“數(shù)目”二字,但不能指出來有哪些在等待,哪些資源可用。 ?因此,本人認為,這個信號量類如果能返回數(shù)目,還能知道哪些對象在等待,哪些資源可使用,就非常完美了,僅僅拿到這些概括性的數(shù)字,對精確控制意義不是很大。目前還沒想到更好的用法。下面是一個簡單的例子:
package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; /** * Java線程:信號量 * * */ public class Test { public static void main(String[] args) { MyPool myPool = new MyPool(20); //創(chuàng)建線程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); MyThread t1 = new MyThread("任務A", myPool, 3); MyThread t2 = new MyThread("任務B", myPool, 12); MyThread t3 = new MyThread("任務C", myPool, 7); //在線程池中執(zhí)行任務 threadPool.execute(t1); threadPool.execute(t2); threadPool.execute(t3); //關閉池 threadPool.shutdown(); } } /** * 一個池 */ class MyPool { private Semaphore sp; //池相關的信號量 /** * 池的大小,這個大小會傳遞給信號量 * * @param size 池的大小 */ MyPool(int size) { this.sp = new Semaphore(size); } public Semaphore getSp() { return sp; } public void setSp(Semaphore sp) { this.sp = sp; } } class MyThread extends Thread { private String threadName; //線程的名稱 private MyPool pool; //自定義池 private int x; //申請信號量的大小 MyThread(String threadName, MyPool pool, int x) { this.threadName = threadName; this.pool = pool; this.x = x; } public void run() { try { //從此信號量獲取給定數(shù)目的許可 pool.getSp().acquire(x); //todo:也許這里可以做更復雜的業(yè)務 System.out.println(threadName + "成功獲取了" + x + "個許可!"); } catch (InterruptedException e) { e.printStackTrace(); } finally { //釋放給定數(shù)目的許可,將其返回到信號量。 pool.getSp().release(x); System.out.println(threadName + "釋放了" + x + "個許可!"); } } }運行結果:
任務A成功獲取了3個許可! 任務B成功獲取了12個許可! 任務B釋放了12個許可! 任務C成功獲取了7個許可! 任務A釋放了3個許可! 任務C釋放了7個許可!從結果可以看出,信號量僅僅是對池資源進行監(jiān)控,但不保證線程的安全,因此,在使用時候,應該自己控制線程的安全訪問池資源。
五、阻塞隊列
??? 阻塞隊列是Java5線程新特征中的內容,Java定義了阻塞隊列的接口java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,如果隊列滿了,添加新元素的操作會被阻塞等待,直到有空位為止。同樣,當隊列為空時候,請求隊列元素的操作同樣會阻塞等待,直到有可用元素為止。 有了這樣的功能,就為多線程的排隊等候的模型實現(xiàn)開辟了便捷通道,非常有用。 java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,可以參看API文檔。 下面給出一個簡單應用的例子: package MultiThread; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; /** * Java線程:并發(fā)庫-阻塞隊列 * * */ public class Test { public static void main(String[] args) throws InterruptedException { BlockingQueue<Integer> bqueue = new ArrayBlockingQueue<Integer>(20); for (int i = 0; i < 30; i++) { //將指定元素添加到此隊列中,如果沒有可用空間,將一直等待(如果有必要)。 bqueue.put(i); System.out.println("向阻塞隊列中添加了元素:" + i); } System.out.println("程序到此運行結束,即將退出----"); } }運行結果:
六、阻塞棧
對于阻塞棧,與阻塞隊列相似。不同點在于棧是“后入先出”的結構,每次操作的是棧頂,而隊列是“先進先出”的結構,每次操作的是隊列頭。 這里要特別說明一點的是,阻塞棧是Java6的新特征。、 Java為阻塞棧定義了接口:java.util.concurrent.BlockingDeque,其實現(xiàn)類也比較多,具體可以查看JavaAPI文檔。 下面看一個簡單例子: package MultiThread;import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; class HelloWorldThread { /** * Java線程:并發(fā)庫-阻塞棧 * * */ public static void main(String[] args) { BlockingDeque<Integer> bstack=new LinkedBlockingDeque<Integer>(20) ; for(int i=0;i<30;i++){ //將指定元素添加到阻塞棧中,如果沒有可用空間,將一直等待(如果有必要)。 bstack.push(i); System.out.println("向阻塞棧中添加了元素:" + i); if(bstack.size()==20){ System.out.println("隊列滿,彈出"+bstack.pop()); System.out.println("隊列滿,彈出"+bstack.pop()); System.out.println("隊列滿,彈出"+bstack.pop()); } } System.out.println("程序到此運行結束,即將退出----"); } }程序的運行結果和阻塞隊列的運行結果一樣,程序并沒結束,二是阻塞住了,原因是棧已經滿了,后面追加元素的操作都被阻塞了。
七、條件變量
????條件變量是Java5線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。但是必須說明,這里的條件是沒有實際含義的,僅僅是個標記而已,并且條件的含義往往通過代碼來賦予其含義。這里的條件和普通意義上的條件表達式有著天壤之別。條件變量都實現(xiàn)了java.util.concurrent.locks.Condition接口,條件變量的實例化是通過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。因此,Java中的條件變量只能和鎖配合使用,來控制并發(fā)程序訪問競爭資源的安全。 ????條件變量的出現(xiàn)是為了更精細控制線程等待與喚醒,在Java5之前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個線程等待,通過調用await()方法,可以讓線程在該條件下等待。當調用signalAll()方法,又可以喚醒該條件下的等待的線程。有關Condition接口的API可以具體參考JavaAPI文檔。 條件變量比較抽象,原因是他不是自然語言中的條件概念,而是程序控制的一種手段。 ????? 下面以一個銀行存取款的模擬程序為例來揭蓋Java多線程條件變量的神秘面紗: 有一個賬戶,多個用戶(線程)在同時操作這個賬戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操作都將等待里面有足夠存款才執(zhí)行操作。 package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Java線程:條件變量 * * @author leizhimin 2009-11-5 10:57:29 */ public class Test { public static void main(String[] args) { //創(chuàng)建并發(fā)訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創(chuàng)建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(2); Thread t1 = new SaveThread("張三", myCount, 2000); Thread t2 = new SaveThread("李四", myCount, 3600); Thread t3 = new DrawThread("王五", myCount, 2700); Thread t4 = new SaveThread("老張", myCount, 600); Thread t5 = new DrawThread("老牛", myCount, 1300); Thread t6 = new DrawThread("胖子", myCount, 800); //執(zhí)行各個線程 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); //關閉線程池 pool.shutdown(); } } /** * 存款線程類 */ class SaveThread extends Thread { private String name; //操作人 private MyCount myCount; //賬戶 private int x; //存款金額 SaveThread(String name, MyCount myCount, int x) { this.name = name; this.myCount = myCount; this.x = x; } public void run() { myCount.saving(x, name); } } /** * 取款線程類 */ class DrawThread extends Thread { private String name; //操作人 private MyCount myCount; //賬戶 private int x; //存款金額 DrawThread(String name, MyCount myCount, int x) { this.name = name; this.myCount = myCount; this.x = x; } public void run() { myCount.drawing(x, name); } } /** * 普通銀行賬戶,不可透支 */ class MyCount { private String oid; //賬號 private int cash; //賬戶余額 private Lock lock = new ReentrantLock(); //賬戶鎖 private Condition _save = lock.newCondition(); //存款條件 private Condition _draw = lock.newCondition(); //取款條件 MyCount(String oid, int cash) { this.oid = oid; this.cash = cash; } /** * 存款 * * @param x 操作金額 * @param name 操作人 */ public void saving(int x, String name) { lock.lock(); //獲取鎖 if (x > 0) { cash += x; //存款 System.out.println(name + "存款" + x + ",當前余額為" + cash); } _draw.signalAll(); //喚醒所有取款等待線程 lock.unlock(); //釋放鎖 } /** * 取款 * * @param x 操作金額 * @param name 操作人 */ public void drawing(int x, String name) { lock.lock(); //獲取鎖 try { if (cash - x < 0) { _draw.await(); //阻塞取款操作 } else { cash -= x; //取款 System.out.println(name + "取款" + x + ",當前余額為" + cash); } _save.signalAll(); //喚醒所有存款操作線程 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //釋放鎖 } } }當然,除了使用并發(fā)庫來實現(xiàn)存取款操作,我們也可以使用synchronized的方法、synchronized的代碼塊來實現(xiàn)。對比并發(fā)庫、synchronized方法、synchronized代碼塊,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。
八、原子量
???? 所謂的原子量即操作變量的操作是“原子的”,該操作不可再分,因此是線程安全的。為何要使用原子變量呢,原因是多個線程對單個變量操作也會引起一些問題。在Java5之前,可以通過volatile、synchronized關鍵字來解決并發(fā)訪問的安全問題,但這樣太麻煩。Java5之后,專門提供了用來進行單變量多線程并發(fā)安全訪問的工具包java.util.concurrent.atomic,其中的類也很簡單。 下面給出一個反面例子(切勿模仿): package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; /** * Java線程:新特征-原子量 * * */ public class Test { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); Runnable t1 = new MyRunnable("張三", 2000); Runnable t2 = new MyRunnable("李四", 3600); Runnable t3 = new MyRunnable("王五", 2700); Runnable t4 = new MyRunnable("老張", 600); Runnable t5 = new MyRunnable("老牛", 1300); Runnable t6 = new MyRunnable("胖子", 800); //執(zhí)行各個線程 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); //關閉線程池 pool.shutdown(); } } class MyRunnable implements Runnable { private static AtomicLong aLong = new AtomicLong(10000); //原子量,每個線程都可以自由操作 private String name; //操作人 private int x; //操作數(shù)額 MyRunnable(String name, int x) { this.name = name; this.x = x; } public void run() { System.out.println(name + "執(zhí)行了" + x + ",當前余額:" + aLong.addAndGet(x)); } }運行結果一:
李四執(zhí)行了3600,當前余額:13600 張三執(zhí)行了2000,當前余額:15600 老張執(zhí)行了600,當前余額:18900 老牛執(zhí)行了1300,當前余額:20200 胖子執(zhí)行了800,當前余額:21000 王五執(zhí)行了2700,當前余額:18300運行結果二:
張三執(zhí)行了2000,當前余額:12000 王五執(zhí)行了2700,當前余額:14700 老張執(zhí)行了600,當前余額:15300 老牛執(zhí)行了1300,當前余額:16600 胖子執(zhí)行了800,當前余額:17400 李四執(zhí)行了3600,當前余額:21000運行結果三:
張三執(zhí)行了2000,當前余額:12000 王五執(zhí)行了2700,當前余額:18300 老張執(zhí)行了600,當前余額:18900 老牛執(zhí)行了1300,當前余額:20200 胖子執(zhí)行了800,當前余額:21000 李四執(zhí)行了3600,當前余額:15600 從運行結果可以看出,雖然使用了原子量,但是程序并發(fā)訪問還是有問題,那究竟問題出在哪里了? 這里要注意的一點是,原子量雖然可以保證單個變量在某一個操作過程的安全,但無法保證你整個代碼塊,或者整個程序的安全性。因此,通常還應該使用鎖等同步機制來控制整個程序的安全性。 下面是對上述代碼的修正: package MultiThread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Java線程:并發(fā)庫-原子量 * * */ public class Test { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); Lock lock=new ReentrantLock(); Runnable t1 = new MyRunnable("張三", 2000,lock); Runnable t2 = new MyRunnable("李四", 3600,lock); Runnable t3 = new MyRunnable("王五", 2700,lock); Runnable t4 = new MyRunnable("老張", 600,lock); Runnable t5 = new MyRunnable("老牛", 1300,lock); Runnable t6 = new MyRunnable("胖子", 800,lock); //執(zhí)行各個線程 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); //關閉線程池 pool.shutdown(); } } class MyRunnable implements Runnable { private static AtomicLong aLong = new AtomicLong(10000); //原子量,每個線程都可以自由操作 private String name; //操作人 private int x; //操作數(shù)額 private Lock lock; MyRunnable(String name, int x,Lock lock) { this.name = name; this.x = x; this.lock=lock; } public void run() { lock.lock(); System.out.println(name + "執(zhí)行了" + x + ",當前余額:" + aLong.addAndGet(x)); lock.unlock(); } }運行結果:
張三執(zhí)行了2000,當前余額:12000 李四執(zhí)行了3600,當前余額:15600 王五執(zhí)行了2700,當前余額:18300 老張執(zhí)行了600,當前余額:18900 老牛執(zhí)行了1300,當前余額:20200 胖子執(zhí)行了800,當前余額:21000 這里使用了一個對象鎖,來控制對并發(fā)代碼的訪問。不管運行多少次,執(zhí)行次序如何,最終余額均為21000,這個結果是正確的。 有關原子量的用法很簡單,關鍵是對原子量的認識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。九、障礙器
??? ?Java5中,添加了障礙器類,為了適應一種新的設計需求,比如一個大型的任務,常常需要分配好多子任務去執(zhí)行,只有當所有子任務都執(zhí)行完成時候,才能執(zhí)行主任務,這時候,就可以選擇障礙器了。障礙器是多線程并發(fā)控制的一種手段,用法很簡單。
下面給個例子:
package MultiThread; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * Java線程:新特征-障礙器 * * */ public class Test { public static void main(String[] args) { //創(chuàng)建障礙器,并設置MainTask為所有定數(shù)量的線程都達到障礙點時候所要執(zhí)行的任務(Runnable) CyclicBarrier cb = new CyclicBarrier(7, new MainTask()); new SubTask("A", cb).start(); new SubTask("B", cb).start(); new SubTask("C", cb).start(); new SubTask("D", cb).start(); new SubTask("E", cb).start(); new SubTask("F", cb).start(); new SubTask("G", cb).start(); } } /** * 主任務 */ class MainTask implements Runnable { public void run() { System.out.println(">>>>主任務執(zhí)行了!<<<<"); } } /** * 子任務 */ class SubTask extends Thread { private String name; private CyclicBarrier cb; SubTask(String name, CyclicBarrier cb) { this.name = name; this.cb = cb; } public void run() { System.out.println("[子任務" + name + "]開始執(zhí)行了!"); for (int i = 0; i < 999999; i++) ; //模擬耗時的任務 System.out.println("[子任務" + name + "]開始執(zhí)行完成了,并通知障礙器已經完成!"); try { //通知障礙器已經完成,讓出鎖(并使得,跳躍的障礙數(shù)目-1) cb.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }運行結果:
[子任務B]開始執(zhí)行了! [子任務E]開始執(zhí)行了! [子任務C]開始執(zhí)行了! [子任務D]開始執(zhí)行了! [子任務A]開始執(zhí)行了! [子任務E]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務B]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務A]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務C]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務D]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務F]開始執(zhí)行了! [子任務F]開始執(zhí)行完成了,并通知障礙器已經完成! [子任務G]開始執(zhí)行了! [子任務G]開始執(zhí)行完成了,并通知障礙器已經完成! >>>>主任務執(zhí)行了!<<<<從執(zhí)行結果可以看出,所有子任務完成的時候,主任務執(zhí)行了,達到了控制的目標
總結:
Java線程是Java語言中一個非常重要的部分,Java5之前,多線程的語言支持還是比較弱的,內容也較少,寫一個復雜的多線程程序是相當有挑戰(zhàn)性的。 在Java5以后,Java對多線程做了很多擴展,擴展部分稱之為并發(fā)包。這部分內容大大增強了Java多線程編程的能力,通過使用Java5線程新特征的API,可以很容易的做出復雜的多線程程序。與其他語言相比,已經是相當強悍了。轉載于:https://www.cnblogs.com/eric-shao/p/11173576.html
總結
以上是生活随笔為你收集整理的Java线程新特征——Java并发库的全部內容,希望文章能夠幫你解決所遇到的問題。