這篇文章主要是關於java.util.concurrent類包下的常用類,會給出相應的介紹並給出實例。
JUC是JDK5才引入的併發類庫。它的基礎就是AbstractQueuedSynchronizer抽象類,Lock,CountDownLatch等的基礎就是該類,而該類又用到了CAS操作。CAS之前已經介紹過。JUC在同步器的設計上有獲取操作和釋放操作。AbstractQueuedSynchronizer(AQS)就是一種同步器的實現。
需要滿足以上兩個操作,需要以下3點來支持:
1、原子操作同步狀態:CAS操作是原子的,進行狀態量的改變。
2、阻塞或者喚醒一個線程:通過LockSupport.park阻塞線程,LockSupport.unpark喚醒線程,它們都是用到Native方法調用底層。
3、內部應該維護一個隊列:採用CLH(三個人名)隊列,Node節點,而線程Thread爲Node節點的一個元素。
1.CountDownLatch
這個類是一個同步計數器,主要用於線程間的控制,當CountDownLatch的count計數>0時,await()會造成阻塞,直到count變爲0,await()結束阻塞,使用countDown()會讓count減1。CountDownLatch的構造函數可以設置count值,當count=1時,它的作用類似於wait()和notify()的作用。如果我想讓其他線程執行完指定程序,其他所有程序都執行結束後我再執行,這時可以用CountDownLatch,但計數無法被重置,如果需要重置計數,請考慮使用 CyclicBarrier 。例子:
import java.util.concurrent.CountDownLatch;
public class cD implements Runnable{
private CountDownLatch begin ;
private CountDownLatch end;
private int s;
public cD(CountDownLatch begin,CountDownLatch end){
this.begin=begin;
this.end=end;
this.s=10;
}
@Override
public synchronized void run(){ //線程同步用了synchronized否則無法保證s的正確性
try{ //注意:await用在synchronized可能導致死鎖,如果換成CyclicBarrier類會導致死鎖
begin.await(); //等待begin統一
System.out.println(s+" 次");
s--;
//do something
}
catch(Exception e){
e.printStackTrace();
}
finally{
end.countDown();
}
}
public static void main(String[] args) throws Exception {
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(10);
cD juc=new cD(begin,end);
for(int j=0;j<10;j++){
new Thread(juc).start(); //開啓10個線程,10個線程分別倒數1次,並讓end減一次
}
//開始倒數計時,開啓begin的countDown()方法
System.out.println("開始倒數計數!。。。10次");
begin.countDown(); //begin減到0,則開始10個線程裏面的await()方法後面的程序
end.await(); //阻塞程序,知道end減爲0
System.out.println("倒數結束。。。後面的程序開始運行");
//do others
}
}
開啓10個線程,共用juc類,對它的s每個線程減1,且對end對象的count減1,當end的count變爲0時,倒數結束:
2.CyclicBarrier
該類從字面理解爲循環屏障,它可以協同多個線程,讓多個線程在這個屏障前等到,直到所有線程都到達了這個屏障時,再一起執行後面的操作。假如每個線程各有一個await,任何一個線程運行到await方法時就阻塞,直到最後一個線程運行到await時才同時返回。和之前的CountDownLatch相比,它只有await方法,而CountDownLatch是使用countDown()方法將計數器減到0,它創建的參數就是countDown的數量;CyclicBarrier創建時的int參數是await的數量。將上面的例子改爲CyclicBarrier:小心與synchronized和Lock互斥鎖公用,可能會導致死鎖,await()方法不會釋放鎖。我一開始編的就發生了死鎖。
import java.util.concurrent.CyclicBarrier;
public class cD implements Runnable{
private CyclicBarrier end;
private int s;
public cD(CyclicBarrier end,int i){
this.end=end;
s=i;
}
@Override
public void run(){ //線程同步用了synchronized否則無法保證s的正確性
try{
System.out.println(s+"隊準備完畢");
s--;
end.await();
}
catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
CyclicBarrier end = new CyclicBarrier(11); //await爲10+1=11個
for(int j=0;j<10;j++){
new Thread(new cD(end,j)).start(); //開啓10個線程
}
end.await(); //阻塞程序,直到10個子線程全部運行的await處
System.out.println("\n10隊全部準備結束。。。後面的程序開始運行");
//do others
}
}
結果:
3.Semaphore
該類用於控制信號量的個數,構造時傳入個數。總數就是控制併發的數量。假如是5,程序執行前用acquire()方法獲得信號,則可用信號變爲4,程序執行完通過release()方法歸還信號量,可用信號又變爲5.如果可用信號爲0,acquire就會造成阻塞,等待release釋放信號。acquire和release方法可以不在同一個線程使用。Semaphore實現的功能就類似廁所有5個坑,假如有10個人要上廁所,那麼同時只能有多少個人去上廁所呢?同時只能有5個人能夠佔用,當5個人中 的任何一個人讓開後,其中等待的另外5個人中又有一個人可以佔用了。另外等待的5個人中可以是隨機獲得優先機會,也可以是按照先來後到的順序獲得機會,這取決於構造Semaphore對象時傳入的參數選項。單個信號量的Semaphore對象可以實現互斥鎖的功能,並且可以是由一個線程獲得了“鎖”,再由另一個線程釋放“鎖”,這可應用於死鎖恢復的一些場合。
例:
import java.util.concurrent.Semaphore;
public class cD implements Runnable{
private Semaphore sema;
private int s;
public cD(Semaphore sema,int i){
this.sema=sema;
s=i;
}
@Override
public void run(){ //線程同步用了synchronized否則無法保證s的正確性
try{
sema.acquire(); //獲取一個控制信號
System.out.println(s+"隊準備完畢");
s--;
}
catch(Exception e){
e.printStackTrace();
}
finally{
sema.release(); //釋放信號
}
}
public static void main(String[] args) throws Exception {
Semaphore sema = new Semaphore(2);
for(int j=0;j<4;j++){
new Thread(new cD(sema,j)).start(); //開啓5個線程,5個線程分別獲得一個信號量,然後釋放
}
}
}
結果:
4.Exchanger
這個類用於交換數據,只能用於兩個線程。當一個線程運行到exchange()方法時會阻塞,另一個線程運行到exchange()時,二者交換數據,然後執行後面的程序。
import java.util.concurrent.Exchanger;
public class JUCTest implements Runnable{
private Exchanger<String> exchange ;
private String name;
private String str;
public JUCTest(Exchanger<String> exchange,String name,String str){
this.exchange=exchange;
this.name=name;
this.str=str;
}
@Override
public void run(){ //線程同步用了synchronized否則無法保證s的正確性
try{
System.out.println(name+"線程自己的數據時:"+str);
String s=exchange.exchange(str); //交換數據
System.out.println(name+"獲取另一個線程的數據:"+s);
}
catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Exchanger<String> ex=new Exchanger<String>();
new Thread(new JUCTest(ex,"zhou","Hello")).start();
new Thread(new JUCTest(ex,"yu","World")).start();
}
}
結果:
5.Future
Future是一個接口。Future模式可以這樣來描述:我有一個任務,提交給了Future,Future替我完成這個任務。期間我自己可以去做任何想做的事情。一段時間之後,我就便可以從Future那兒取出結果。就相當於下了一張訂貨單,一段時間後可以拿着提訂單來提貨,這期間可以幹別的任何事情。其中Future 接口就是訂貨單,真正處理訂單的是Executor類,它根據Future接口的要求來生產產品。它經常配合線程池來一起工作,將任務交給線程池去處理。
FutureTask是一個實現類,它實現了Runnable接口,配合Callable接口創建線程。Callable接口的call()方法作爲線程執行體,call可以有返回值,也可以拋出異常。Callable對象不能直接作爲Thread的目標,但用Future可以完成。
例子:
public static void main(String[] args)throws Exception{
FutureTask<Integer> task=new FutureTask<Integer>(new Callable(){public Integer call(){ return 1+2+3+4;}}); //匿名內部類
new Thread(task).start();
System.out.println("做自己的事,計算1+2+3+4交給另一個線程完成\n");
System.out.println("返回獲取結果 "+task.get());
}
結果:
做自己的事,計算1+2+3+4交給另一個線程完成
返回獲取結果 10
參考:
http://blog.csdn.net/aesop_wubo/article/details/7553520
http://www.cnblogs.com/whgw/archive/2011/09/29/2195555.html