Java多線程---JUC包下的常見類

  這篇文章主要是關於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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章