这篇文章主要是关于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