Java線程新特征——Java并發庫

一、線程池

??Sun在Java5中,對Java線程的類庫做了大量的擴展,其中線程池就是Java5的新特征之一,除了線程池之外,還有很多多線程相關的內容,為多線程的編程帶來了極大便利。為了編寫高效穩定可靠的多線程程序,線程部分的新增內容顯得尤為重要。
????有關Java5線程新特征的內容全部在java.util.concurrent下面,里面包含數目眾多的接口和類,熟悉這部分API特征是一項艱難的學習過程。當然新特征對做多線程程序沒有必須的關系,在java5之前通用可以寫出很優秀的多線程程序。只是代價不一樣而已。
線程池的基本思想還是一種對象池的思想,開辟一塊內存空間,里面存放了眾多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成后線程對象歸池,這樣可以避免反復創建線程對象所帶來的性能開銷,節省了系統的資源。
????在Java5之前,要實現一個線程池是相當有難度的,現在Java5為我們做好了一切,我們只需要按照提供的API來使用,即可享受線程池帶來的極大便利。
Java5的線程池分好多種:固定尺寸的線程池可變尺寸連接池
?
在使用線程池之前,必須知道如何去創建一個線程池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量創建連接池的靜態方法,是必須掌握的。

a、固定大小的線程池

import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; /** 
* Java線程:線程池- 
* 
* */ 
public class Test { public static void main(String[] args) { //創建一個可重用固定線程數的線程池 ExecutorService pool = Executors.newFixedThreadPool(2); //創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //將線程放入池中進行執行 
                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()+"正在執行..."); } 
}

運行結果:

pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-2正在執行...
pool-1-thread-1正在執行...
pool-1-thread-2正在執行...

?如果將線程池的大小改為4,則運行結果如下:

pool-1-thread-2正在執行...
pool-1-thread-3正在執行...
pool-1-thread-3正在執行...
pool-1-thread-2正在執行...
pool-1-thread-1正在執行...

?

b、單任務線程池

在上例的基礎上改一行創建pool對象的代碼為:

 //創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。 
ExecutorService pool = Executors.newSingleThreadExecutor(); 

則,運行結果為:

pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
對于以上兩種連接池,大小都是固定的,當要加入的池的線程(或者任務)超過池最大尺寸時候,則入此線程池需要排隊等待。
一旦池中有線程完畢,則排隊等待的某個線程會入池執行。

?c、可變尺寸的線程池

?與上面的類似,只是改動下pool的創建方式:

  //創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。 ExecutorService pool = Executors.newCachedThreadPool(); 

運行結果如下:

pool-1-thread-1正在執行...
pool-1-thread-5正在執行...
pool-1-thread-4正在執行...
pool-1-thread-3正在執行...
pool-1-thread-2正在執行...

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) { //創建一個線程池,它可那排在給定延遲后運行命令或者定期地執行ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);//創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口 Thread t1 = new MyThread(); Thread t2 = new MyThread(); Thread t3 = new MyThread(); Thread t4 = new MyThread(); Thread t5 = new MyThread(); //將線程放入池中進行執行 
                pool.execute(t1); pool.execute(t2); pool.execute(t3); //使用延遲執行風格的方法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()+"正在執行..."); } 
}

e、單任務連接線程池

在d的代碼基礎上,做改動

 //創建一個單線程執行程序,它可安排在給定延遲后運行命令或者定期地執行。 ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();

運行時,會發現,t4延遲5s后得到執行,t5延遲10s后得到執行。運行結果如下:

pool-1-thread-2正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-2正在執行...
pool-1-thread-1正在執行...

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) { //創建等待隊列 BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20); //創建一個單線程執行程序,它可安排在給定延遲后運行命令或者定期地執行。 ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue); //創建實現了Runnable接口對象,Thread對象當然也實現了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(); //將線程放入池中進行執行 
                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() + "正在執行..."); try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } } 
}

運行結構如下:
創建自定義線程池的構造方法很多,本例中參數的含義如下:

ThreadPoolExecutor

public ThreadPoolExecutor(int?corePoolSize,int?maximumPoolSize,long?keepAliveTime,TimeUnit?unit,BlockingQueue<Runnable>?workQueue)
用給定的初始參數和默認的線程工廠及處理程序創建新的 ThreadPoolExecutor。使用 Executors 工廠方法之一比使用此通用構造方法方便得多。?
參數:
corePoolSize - 池中所保存的線程數,包括空閑線程。?
maximumPoolSize - 池中允許的最大線程數。?
keepAliveTime - 當線程數大于核心時,此為終止前多余的空閑線程等待新任務的最長時間。?
unit - keepAliveTime 參數的時間單位。?
workQueue - 執行前用于保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。?
拋出:
IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小于零,或者 maximumPoolSize 小于或等于零,或者 corePoolSize 大于 maximumPoolSize。?
NullPointerException - 如果 workQueue 為 null
自定義連接池稍微麻煩些,不過通過創建的ThreadPoolExecutor線程池對象,可以獲取到當前線程池的尺寸、正在執行任務的線程數、工作隊列等等。

二、有返回值的線程

在Java5之前,線程是沒有返回值的,常常為了“有”返回值,破費周折,而且代碼很不好寫。或者干脆繞過這道坎,走別的路了。現在Java終于有可返回值的線程了。
?可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須實現Runnable接口。
?執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。

?下面是一個簡單的例子:

?

package MultiThread;
import java.util.concurrent.*; /** 
* Java線程:有返回值的線程 
* 
* 
*/ 
public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //創建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(2); //創建兩個有返回值的任務 Callable<String> c1 = new MyCallable("A"); Callable<String> c2 = new MyCallable("B"); //執行任務并獲取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啊。

三、并發庫的鎖

在Java5中,專門提供了鎖對象,利用鎖可以方便的實現資源的封鎖,用來控制對競爭資源并發訪問的控制,這些內容主要集中在java.util.concurrent.locks 包下面,里面有三個重要的接口Condition、Lock、ReadWriteLock。

接口摘要
ConditionConditionObject 監視器方法(waitnotifynotifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。
LockLock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
ReadWriteLockReadWriteLock 維護了一對相關的,一個用于只讀操作,另一個用于寫入操作。

?

?

?

?

?有關鎖的介紹,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) { //創建并發訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創建一個鎖對象 Lock lock = new ReentrantLock(); //創建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); //創建一些并發訪問用戶,一個信用卡,存的存,取的取,好熱鬧啊 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); //在線程池中執行各個用戶的操作 
                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;                //執行操作所需的鎖對象 
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(); //執行現金業務 System.out.println(threadName + "正在操作" + myCount + "賬戶,操作金額為" + iocash + ",當前金額為" + myCount.getCash()); myCount.setCash(myCount.getCash() + iocash); System.out.println("\t操作成功,操作金額為" + iocash + ",當前金額為" + myCount.getCash()); //釋放鎖,否則別的線程沒有機會執行了 
                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
從上面的輸出可以看到,利用鎖對象太方便了,比直接在某個不知情的對象上用鎖清晰多了。但一定要注意的是,在獲取了鎖對象后,用完后應該盡快釋放鎖,以便別的等待該鎖的線程有機會去執行。

b、讀寫鎖

?在a中提到了Lock接口以及對象,使用它可以很優雅的控制了競爭資源的安全訪問,但是這種鎖不區分讀寫,稱這種鎖為普通鎖。為了提高性能,Java提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在一定程度上提高了程序的執行效率。
Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現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) { //創建并發訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創建一個鎖對象 ReadWriteLock lock = new ReentrantReadWriteLock(); //創建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); //創建一些并發訪問用戶線程,一個信用卡,存的存,取的取,好熱鬧啊 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);//在線程池中執行各個用戶的操作 
                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;                //執行操作所需的鎖對象 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(); //執行查詢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

在實際開發中,最好在能用讀寫鎖的情況下使用讀寫鎖,而不要用普通鎖,以求更好的性能。

?四、信號量

Java的信號量實際上是一個功能完畢的計數器,對控制一定資源的消費與回收有著很重要的意義,信號量常常用于多線程的代碼中,并能監控有多少數目的線程等待獲取資源,并且通過信號量可以得知可用資源的數目等等,這里總是在強調“數目”二字,但不能指出來有哪些在等待,哪些資源可用。
?因此,本人認為,這個信號量類如果能返回數目,還能知道哪些對象在等待,哪些資源可使用,就非常完美了,僅僅拿到這些概括性的數字,對精確控制意義不是很大。目前還沒想到更好的用法。

下面是一個簡單的例子:

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); //創建線程池 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); //在線程池中執行任務 
                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 { //從此信號量獲取給定數目的許可 
                        pool.getSp().acquire(x); //todo:也許這里可以做更復雜的業務 System.out.println(threadName + "成功獲取了" + x + "個許可!"); } catch (InterruptedException e) { e.printStackTrace(); } finally { //釋放給定數目的許可,將其返回到信號量。 
                        pool.getSp().release(x); System.out.println(threadName + "釋放了" + x + "個許可!"); } } 
}

運行結果:

任務A成功獲取了3個許可!
任務B成功獲取了12個許可!
任務B釋放了12個許可!
任務C成功獲取了7個許可!
任務A釋放了3個許可!
任務C釋放了7個許可!

從結果可以看出,信號量僅僅是對池資源進行監控,但不保證線程的安全,因此,在使用時候,應該自己控制線程的安全訪問池資源。

五、阻塞隊列

??? 阻塞隊列是Java5線程新特征中的內容,Java定義了阻塞隊列的接口java.util.concurrent.BlockingQueue,阻塞隊列的概念是,一個指定長度的隊列,如果隊列滿了,添加新元素的操作會被阻塞等待,直到有空位為止。同樣,當隊列為空時候,請求隊列元素的操作同樣會阻塞等待,直到有可用元素為止。
有了這樣的功能,就為多線程的排隊等候的模型實現開辟了便捷通道,非常有用。
java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,可以參看API文檔。
下面給出一個簡單應用的例子:
package MultiThread;
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ArrayBlockingQueue; /** 
* Java線程:并發庫-阻塞隊列 
* 
* 
*/ 
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("程序到此運行結束,即將退出----"); } 
}

運行結果:

由于阻塞隊列的大小為20個,當超過這個數目,又沒有元素出隊列的時候,隊列將會阻塞。到后來的某一個時刻,程序將阻塞隊列中的元素出隊列,后面的元素才可以進隊列。

六、阻塞棧

對于阻塞棧,與阻塞隊列相似。不同點在于棧是“后入先出”的結構,每次操作的是棧頂,而隊列是“先進先出”的結構,每次操作的是隊列頭。
這里要特別說明一點的是,阻塞棧是Java6的新特征。、
Java為阻塞棧定義了接口:java.util.concurrent.BlockingDeque,其實現類也比較多,具體可以查看JavaAPI文檔。
下面看一個簡單例子:
package MultiThread;import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
class HelloWorldThread {/** * Java線程:并發庫-阻塞棧* * */ 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線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。但是必須說明,這里的條件是沒有實際含義的,僅僅是個標記而已,并且條件的含義往往通過代碼來賦予其含義。這里的條件和普通意義上的條件表達式有著天壤之別。條件變量都實現了java.util.concurrent.locks.Condition接口,條件變量的實例化是通過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。因此,Java中的條件變量只能和鎖配合使用,來控制并發程序訪問競爭資源的安全。
????條件變量的出現是為了更精細控制線程等待與喚醒,在Java5之前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個線程等待,通過調用await()方法,可以讓線程在該條件下等待。當調用signalAll()方法,又可以喚醒該條件下的等待的線程。有關Condition接口的API可以具體參考JavaAPI文檔。
條件變量比較抽象,原因是他不是自然語言中的條件概念,而是程序控制的一種手段。
????? 下面以一個銀行存取款的模擬程序為例來揭蓋Java多線程條件變量的神秘面紗:
有一個賬戶,多個用戶(線程)在同時操作這個賬戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操作都將等待里面有足夠存款才執行操作。
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) { //創建并發訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); //創建一個線程池 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); //執行各個線程 
                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();                     //釋放鎖 
                } } 
}

當然,除了使用并發庫來實現存取款操作,我們也可以使用synchronized的方法、synchronized的代碼塊來實現。對比并發庫、synchronized方法、synchronized代碼塊,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。

八、原子量

???? 所謂的原子量即操作變量的操作是“原子的”,該操作不可再分,因此是線程安全的。為何要使用原子變量呢,原因是多個線程對單個變量操作也會引起一些問題。在Java5之前,可以通過volatile、synchronized關鍵字來解決并發訪問的安全問題,但這樣太麻煩。Java5之后,專門提供了用來進行單變量多線程并發安全訪問的工具包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); //執行各個線程 
                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;                            //操作數額 
MyRunnable(String name, int x) { this.name = name; this.x = x; } public void run() { System.out.println(name + "執行了" + x + ",當前余額:" + aLong.addAndGet(x)); } 
}

運行結果一:

李四執行了3600,當前余額:13600
張三執行了2000,當前余額:15600
老張執行了600,當前余額:18900
老牛執行了1300,當前余額:20200
胖子執行了800,當前余額:21000
王五執行了2700,當前余額:18300

運行結果二:

張三執行了2000,當前余額:12000
王五執行了2700,當前余額:14700
老張執行了600,當前余額:15300
老牛執行了1300,當前余額:16600
胖子執行了800,當前余額:17400
李四執行了3600,當前余額:21000

運行結果三:

張三執行了2000,當前余額:12000 
王五執行了2700,當前余額:18300 
老張執行了600,當前余額:18900 
老牛執行了1300,當前余額:20200 
胖子執行了800,當前余額:21000 
李四執行了3600,當前余額:15600 
從運行結果可以看出,雖然使用了原子量,但是程序并發訪問還是有問題,那究竟問題出在哪里了?
這里要注意的一點是,原子量雖然可以保證單個變量在某一個操作過程的安全,但無法保證你整個代碼塊,或者整個程序的安全性。因此,通常還應該使用鎖等同步機制來控制整個程序的安全性。
下面是對上述代碼的修正:
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線程:并發庫-原子量 
* 
*
*/ 
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); //執行各個線程 
                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;      //操作數額 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 + "執行了" + x + ",當前余額:" + aLong.addAndGet(x)); lock.unlock();} 
}

運行結果:

張三執行了2000,當前余額:12000
李四執行了3600,當前余額:15600
王五執行了2700,當前余額:18300
老張執行了600,當前余額:18900
老牛執行了1300,當前余額:20200
胖子執行了800,當前余額:21000
這里使用了一個對象鎖,來控制對并發代碼的訪問。不管運行多少次,執行次序如何,最終余額均為21000,這個結果是正確的。
有關原子量的用法很簡單,關鍵是對原子量的認識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。

九、障礙器

??? ?Java5中,添加了障礙器類,為了適應一種新的設計需求,比如一個大型的任務,常常需要分配好多子任務去執行,只有當所有子任務都執行完成時候,才能執行主任務,這時候,就可以選擇障礙器了。障礙器是多線程并發控制的一種手段,用法很簡單。

下面給個例子:

package MultiThread;
import java.util.concurrent.BrokenBarrierException; 
import java.util.concurrent.CyclicBarrier; /** 
* Java線程:新特征-障礙器 
* 
*  
*/ 
public class Test { public static void main(String[] args) { //創建障礙器,并設置MainTask為所有定數量的線程都達到障礙點時候所要執行的任務(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(">>>>主任務執行了!<<<<"); } 
} /** 
* 子任務 
*/ 
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 + "]開始執行了!"); for (int i = 0; i < 999999; i++) ;    //模擬耗時的任務 System.out.println("[子任務" + name + "]開始執行完成了,并通知障礙器已經完成!"); try { //通知障礙器已經完成,讓出鎖(并使得,跳躍的障礙數目-1)
                        cb.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } 
}

運行結果:

[子任務B]開始執行了!
[子任務E]開始執行了!
[子任務C]開始執行了!
[子任務D]開始執行了!
[子任務A]開始執行了!
[子任務E]開始執行完成了,并通知障礙器已經完成!
[子任務B]開始執行完成了,并通知障礙器已經完成!
[子任務A]開始執行完成了,并通知障礙器已經完成!
[子任務C]開始執行完成了,并通知障礙器已經完成!
[子任務D]開始執行完成了,并通知障礙器已經完成!
[子任務F]開始執行了!
[子任務F]開始執行完成了,并通知障礙器已經完成!
[子任務G]開始執行了!
[子任務G]開始執行完成了,并通知障礙器已經完成!
>>>>主任務執行了!<<<<

從執行結果可以看出,所有子任務完成的時候,主任務執行了,達到了控制的目標

總結:

Java線程是Java語言中一個非常重要的部分,Java5之前,多線程的語言支持還是比較弱的,內容也較少,寫一個復雜的多線程程序是相當有挑戰性的。
在Java5以后,Java對多線程做了很多擴展,擴展部分稱之為并發包。這部分內容大大增強了Java多線程編程的能力,通過使用Java5線程新特征的API,可以很容易的做出復雜的多線程程序。與其他語言相比,已經是相當強悍了。

轉載于:https://www.cnblogs.com/shudonghe/p/3318494.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/377850.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/377850.shtml
英文地址,請注明出處:http://en.pswp.cn/news/377850.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第一篇博文

剛剛申請博客&#xff0c;開通了&#xff0c;很高興。但是由于這幾天考試比較多&#xff0c;等考完之后&#xff0c;再開始正式寫博客&#xff0c;與諸君共進步&#xff01; 2012/1/1 18:20 轉載于:https://www.cnblogs.com/zhenglichina/archive/2012/01/01/2309561.html

leetcode 40. 組合總和 II 思考分析

題目 給定一個數組 candidates 和一個目標數 target &#xff0c;找出 candidates 中所有可以使數字和為 target 的組合。 candidates 中的每個數字在每個組合中只能使用一次。 思考以及代碼 如果我們直接套用39題的思路&#xff0c;那么就會出現重復的組合。 重復組合的…

java vector_Java Vector size()方法與示例

java vector矢量類size()方法 (Vector Class size() method) size() method is available in java.util package. size()方法在java.util包中可用。 size() method is used to return the size (i.e. the number of the element exists) of this Vector. size()方法用于返回此V…

二、線性回歸

一、回歸 可以拿正態分布為例&#xff0c;比如身高&#xff0c;若平均身高為1.78m&#xff0c;絕大多數人都是1.78m左右&#xff0c;超過2m的很少&#xff0c;低于1m的也不多。 很多事情都會回歸到一定的區間之內&#xff0c;即回歸到平均值。 機器學習沒有完美解&#xff0c…

【轉】HMM學習最佳范例五:前向算法1 .

五、前向算法&#xff08;Forward Algorithm&#xff09; 計算觀察序列的概率&#xff08;Finding the probability of an observed sequence&#xff09; 1.窮舉搜索&#xff08; Exhaustive search for solution&#xff09;  給定隱馬爾科夫模型&#xff0c;也就是在模型參…

vs 字體

看代碼看得眼疼不能不說是程序員的惡夢&#xff0c;那么&#xff0c;選擇適當的字體也算是對自己的救贖吧。周末閑得無聊&#xff0c;在網上亂逛&#xff0c;搜索了一些資料整理一下給大家分享&#xff0c;僅作記錄而已&#xff0c;參考使用&#xff1a; 1.一個編程人員痛苦的選…

leetcode 349. 兩個數組的交集 思考分析

題目 給定兩個數組&#xff0c;編寫一個函數來計算它們的交集。 1、暴力雙for循環 class Solution { public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {vector<int> result;vector<int> res;if(nums1.siz…

random.next_Java Random next()方法與示例

random.next隨機類的next()方法 (Random Class next() method) next() method is available in java.util package. next()方法在java.util包中可用。 next() method is used to return the pseudo-random number in bits. next()方法用于返回以位為單位的偽隨機數。 next() me…

VS2008下QT開發環境搭建

http://blog.csdn.net/sunnyboycao/article/details/6364444 轉載于:https://www.cnblogs.com/bjfuyumu/p/3321180.html

三、梯度下降法求解最優θ值

一、梯度下降法(GD&#xff0c;Gradient Descent) Ⅰ、得到目標函數J(θ)&#xff0c;求解使得J(θ)最小時的θ值 當然&#xff0c;這里只是取了倆特征而已&#xff0c;實際上會有m個特征維度 通過最小二乘法求目標函數最小值 令偏導為0即可求解出最小的θ值&#xff0c;即…

Delphi中Messagedlg用法

if MessageDlg(Welcome to my Delphi application. Exit now?, mtConfirmation, [mbYes, mbNo], 0) mrYes then begin Close; end;MessageDlg用法 對話框類型&#xff1a;mtwarning——含有感嘆號的警告對話框mterror——含有紅色叉符號的錯誤對話框mtinformation——含有藍…

leetcode 131. 分割回文串 思考分析

題目 給定一個字符串 s&#xff0c;將 s 分割成一些子串&#xff0c;使每個子串都是回文串。 返回 s 所有可能的分割方案。 思考 問題可以分為兩個子問題&#xff1a;1、判斷回文串2、分割數組 判斷回文串 bool isPalindrome_string(string s,int startindex,int endinde…

android淡入淡出動畫_在Android中淡入動畫示例

android淡入淡出動畫1) XML File: activity_main 1)XML文件&#xff1a;activity_main <?xml version"1.0" encoding"utf-8"?><android.support.constraint.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android&…

[慢查優化]聯表查詢注意誰是驅動表 你搞不清楚誰join誰更好時請放手讓mysql自行判定...

寫在前面的話&#xff1a; 不要求每個人一定理解 聯表查詢(join/left join/inner join等)時的mysql運算過程&#xff1b; 不要求每個人一定知道線上&#xff08;現在或未來&#xff09;哪張表數據量大&#xff0c;哪張表數據量小&#xff1b; 但把mysql客戶端&#xff08;如SQL…

四、梯度下降歸一化操作

一、歸一化 Ⅰ什么是歸一化&#xff1f; 答&#xff1a;其實就是把數據歸一到0-1之間&#xff0c;也就是縮放。 常用的歸一化操作是最大最小值歸一化&#xff0c;公式如下&#xff1a; 例如&#xff1a;1&#xff0c;3&#xff0c;5&#xff0c;7&#xff0c;9&#xff0c;10…

[轉帖][強烈推薦]網頁表格(Table/GridView)標題欄和列凍結(跨瀏覽器兼容)

GridView的標題欄、列凍結效果(跨瀏覽器版) 本文來源&#xff1a;http://blog.darkthread.net/blogs/darkthreadtw/archive/2009/02/18/supertable-plugin-for-jquery.aspx 稍早發表了GridView 的標題列凍結效果&#xff0c;足以滿足工作上的需求&#xff0c;不過存在兩個缺點:…

psu是什么電腦配件_PSU的完整形式是什么?

psu是什么電腦配件PSU&#xff1a;電源部門/公共部門事業 (PSU: Power Supply Unit / Public Sector Undertaking) 1)PSU&#xff1a;電源設備 (1) PSU: Power Supply Unit) PSU is an abbreviation of the "Power Supply Unit". PSU是“電源設備”的縮寫 。 It is a…

【C++grammar】斷言與表達式常量

目錄1、常量表達式和constexpr關鍵字2、斷言與C11的靜態斷言1.1. assert : C語言的宏(Macro)&#xff0c;運行時檢測。1.2. assert()依賴于NDEBUG 宏1.3. assert 幫助調試解決邏輯bug &#xff08;部分替代“斷點/單步調試”&#xff09;2.1static_assert (C11的靜態斷言 )2.2.…

一些又用的國內著名期刊

記&#xff1a; 電子學報、電子與信息學報、圖像圖形學報、自動化學報、計算機學報、軟件學報、計算機研究與發展。轉載于:https://www.cnblogs.com/nanyangzp/p/3322244.html

一、Arduino UNO R3將數據上傳至云平臺

一、準備工作 ①ESP12E Shield ②Arduino UNO R3開發板 ③把ESP12E Shield安裝到Arduino UNO R3開發板上 ④登錄物聯網平臺注冊個賬號&#xff0c;到時候需要使用。 ⑤記錄下來你的Uid和key到時候會用到 ⑥創建個設備&#xff0c;用于測試 ⑦beyondyanyu為設備名&…