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




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