ReetrantLock与Condition:
在java.util.concurrent包中。有两个非常特殊的工具类。Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurrent包提供的一种独占锁的实现。它继承自Dong Lea的 AbstractQueuedSynchronizer(同步器),确切的说是ReentrantLock的一个内部类继承了AbstractQueuedSynchronizer。ReentrantLock仅仅只是是代理了该类的一些方法,可能有人会问为什么要使用内部类在包装一层? 我想是安全的关系,由于AbstractQueuedSynchronizer中有非常多方法,还实现了共享锁,Condition(稍候再细说)等功能,假设直接使ReentrantLock继承它,则非常easy出现AbstractQueuedSynchronizer中的API被误用的情况。
ReentrantLock和Condition的使用方式一般是这种:
执行后,结果例如以下:
能够看到,
Condition的运行方式,是当在线程1中调用await方法后。线程1将释放锁,而且将自己沉睡。等待唤醒,
线程2获取到锁后。開始做事,完成后,调用Condition的signal方法,唤醒线程1,线程1恢复运行。
以上说明Condition是一个多线程间协调通信的工具类。使得某个,或者某些线程一起等待某个条件(Condition),仅仅有当该条件具备( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而又一次争夺锁。
那。它是怎么实现的呢?
首先还是要明确。reentrantLock.newCondition() 返回的是Condition的一个实现,该类在AbstractQueuedSynchronizer中被实现。叫做newCondition()
关键的就在于此,我们知道AQS自己维护的队列是当前等待资源的队列。AQS会在资源被释放后,依次唤醒队列中从前到后的全部节点,使他们相应的线程恢复运行。直到队列为空。
而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列,两个队列的作用是不同,其实。每一个线程也只会同一时候存在以上两个队列中的一个,流程是这种:
1. 线程1调用reentrantLock.lock时,线程被增加到AQS的等待队列中。
2. 线程1调用await方法被调用时。该线程从AQS中移除,相应操作是锁的释放。
3. 接着立即被增加到Condition的等待队列中,以为着该线程须要signal信号。
4. 线程2,由于线程1释放锁的关系。被唤醒。并推断能够获取锁。于是线程2获取锁。并被增加到AQS的等待队列中。
5. 线程2调用signal方法,这个时候Condition的等待队列中仅仅有线程1一个节点,于是它被取出来,并被增加到AQS的等待队列中。 注意,这个时候,线程1 并没有被唤醒。
6. signal方法运行完成,线程2调用reentrantLock.unLock()方法,释放锁。这个时候由于AQS中仅仅有线程1,于是。AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒。于是线程1回复运行。
7. 直到释放所整个过程运行完成。
能够看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的。Condition作为一个条件类,非常好的自己维护了一个等待信号的队列,并在适时的时候将结点增加到AQS的等待队列中来实现的唤醒操作。
CyclicBarrier: 循环的篱笆。
它同意一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中。这些线程必须不时地互相等待。此时 CyclicBarrier 非常实用。
当某一个线程到达公共屏障点后(即完毕一部分任务后),调用awaite(),等待其它线程到来。一起走。
能够看成是一个集结线程类。构造函数传入等待线程的数量
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CyclicBarrierTest { public static void main(String [] args){ ExecutorService service=Executors.newCachedThreadPool(); final CyclicBarrier cb=new CyclicBarrier(3); //三个线程同一时候到达 for(int i=0;i<3;i++){ Runnable runnable=new Runnable(){ public void run(){ try { Thread.sleep((long)(Math.random()*10000)); System.out.println("线程"+Thread.currentThread().getName()+ "即将到达集合地点1,当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+ (cb.getNumberWaiting()==2?"都到齐了。继续走啊":"正在等候")); try { cb.await(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread.sleep((long)(Math.random()*10000)); System.out.println("线程"+Thread.currentThread().getName()+ "即将到达集合地点2。当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+ (cb.getNumberWaiting()==2?
"都到齐了。继续走啊":"正在等候")); try { cb.await(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } Thread.sleep((long)(Math.random()*10000)); System.out.println("线程"+Thread.currentThread().getName()+ "即将到达集合地点3,当前已有"+(cb.getNumberWaiting()+1)+"个已到达"+ (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); try { cb.await(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; service.execute(runnable); } service.shutdown(); } }
结果:
线程pool-1-thread-3即将到达集合地点1,当前已有1个已到达正在等候
线程pool-1-thread-2即将到达集合地点1,当前已有2个已到达正在等候
线程pool-1-thread-1即将到达集合地点1,当前已有3个已到达都到齐了。继续走啊
线程pool-1-thread-1即将到达集合地点2,当前已有1个已到达正在等候
线程pool-1-thread-2即将到达集合地点2,当前已有2个已到达正在等候
线程pool-1-thread-3即将到达集合地点2。当前已有3个已到达都到齐了。继续走啊
线程pool-1-thread-2即将到达集合地点3,当前已有1个已到达正在等候
线程pool-1-thread-1即将到达集合地点3,当前已有2个已到达正在等候
线程pool-1-thread-3即将到达集合地点3。当前已有3个已到达都到齐了,继续走啊
CountDownLatch: 它同意一个或多个线程一直等待,直到其它线程的操作运行完后再运行。
比如。应用程序的主线程希望在负责启动框架服务的线程已经启动全部的框架服务之后再运行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
每当一个线程完毕了自己的任务后,计数器的值就会减1。当计数器值到达0时。它表示全部的线程已经完毕了任务,然后在闭锁上等待的线程就能够恢复运行任务。
过程:1,主线程开启
2,new出等待N个线程的CountDownLatch(构造函数传入须要等待的线程数量)
3,create并运行N个线程
4。主线程等待
5。N个线程都运行完成
6,主线程恢复运行。
与CountDownLatch的第一次交互是主线程等待其它线程。
主线程必须在启动其它线程后马上调用CountDownLatch对象的await()方法。
这样主线程的操作就会在这种方法上堵塞,直到其它线程完毕各自的任务。
其它N 个线程必须引用闭锁对象,由于他们须要通知CountDownLatch对象。他们已经完毕了各自的任务。
这样的通知机制是通过 CountDownLatch.countDown()方法来完毕的;每调用一次这种方法。在构造函数中初始化的count值就减1。
所以当N个线程都调 用了这种方法,count的值等于0,然后主线程就能通过await()方法,恢复运行自己的任务(一旦其它线程已经開始运行,就能够调用CountDownLatch对象的awaite方法,等待其它线程运行完成后,开启主线程)。
Semaphore翻译成字面意思为 信号量,Semaphore能够控同一时候訪问的线程个数,通过 acquire() 获取一个许可。假设没有就等待,而 release() 释放一个许可。
Semphore:信号量 參考,
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:
1
2
3
4
5
6
|
public Semaphore( int permits)
{ //參数permits表示许可数目,即同一时候能够同意多少线程进行訪问 sync
= new NonfairSync(permits); } public Semaphore( int permits, boolean fair)
{ //这个多了一个參数fair表示是否是公平的,即等待时间越久的越先获取许可 sync
= (fair)? new FairSync(permits)
: new NonfairSync(permits); } |
以下说一下Semaphore类中比較重要的几个方法。首先是acquire()、release()方法:
1
2
3
4
|
public void acquire() throws InterruptedException
{ } //获取一个许可 public void acquire( int permits) throws InterruptedException
{ } //获取permits个许可 public void release()
{ } //释放一个许可 public void release( int permits)
{ } //释放permits个许可 |
acquire()用来获取一个许可,若无许可可以获得。则会一直等待,直到获得许可。
release()用来释放许可。注意。在释放许可之前。必须先获获得许可。
这4个方法都会被堵塞,假设想马上得到运行结果。能够使用以下几个方法:
1
2
3
4
|
public boolean tryAcquire()
{ }; //尝试获取一个许可。若获取成功。则马上返回true。若获取失败。则马上返回false public boolean tryAcquire( long timeout,
TimeUnit unit) throws InterruptedException
{ }; //尝试获取一个许可,若在指定的时间内获取成功,则马上返回true。否则则马上返回false public boolean tryAcquire( int permits)
{ }; //尝试获取permits个许可。若获取成功,则马上返回true,若获取失败,则马上返回false public boolean tryAcquire( int permits, long timeout,
TimeUnit unit) throws InterruptedException
{ }; //尝试获取permits个许可,若在指定的时间内获取成功,则马上返回true,否则则马上返回false |
另外还能够通过availablePermits()方法得到可用的许可数目。
以下通过一个样例来看一下Semaphore的详细使用:
假若一个工厂有5台机器。可是有8个工人。一台机器同一时候仅仅能被一个工人使用,仅仅有使用完了,其它工人才干继续使用。(有人将这个类的应用归结为厕所理论。事实上都是一样的)那么我们就能够通过Semaphore来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class Test
{ public static void main(String[]
args) { int N
= 8 ; //工人数 Semaphore
semaphore = new Semaphore( 5 ); //机器数目 for ( int i= 0 ;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore
semaphore; public Worker( int num,Semaphore
semaphore){ this .num
= num; this .semaphore
= semaphore; } @Override public void run()
{ try { semaphore.acquire(); System.out.println( "工人" + this .num+ "占用一个机器在生产..." ); Thread.sleep( 2000 ); System.out.println( "工人" + this .num+ "释放出机器" ); semaphore.release(); } catch (InterruptedException
e) { e.printStackTrace(); } } } } |
1)CountDownLatch和CyclicBarrier都可以实现线程之间的等待,仅仅只是它们側重点不同:
CountDownLatch一般用于某个线程A等待若干个其它线程运行完任务之后,它才运行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同一时候运行;
另外,CountDownLatch是不可以重用的。而CyclicBarrier是可以重用的。
2)Semaphore事实上和锁有点类似,它一般用于控制对某组资源的訪问权限。