同步计数器之--CountDownLatch
使用场景:当用户上传文件,我们可以多开几个线程来上传文件,当所有的线程上传完成后,文件才算上传完成,在此场景下可以使用CountDownLatch,当每个线程上传完成,调用CountDownLatch.countDown()使计数器减1,当计数器为0时表示文件上传完成。
CountDownLatch的同步器是通过AQS来实现同步的,在CountDownLatch内部定义了一个Sycn类,此类继承AQS来实现同步,我们来看下CountDownLatch的几个主要方法的执行过程
1).当CountDownLatch调用countDown()时
//CountDownLatch内部调用静态内部类Sync的方法,但是Sync并没有releaseShared(int i)的实现,所以我们查看它的父类AQS
public void countDown() {
sync.releaseShared(1);
}
AbstractQueuedSynchronizer类
//在AQS中调用子类的tryReleaseShared(int i)的方法,如果子类的方法返回true,则 doReleaseShared(),并且自身返回true
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();//释放信号量
return true;
}
return false;
}
Sync类
//
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();//获取当前计数器
if (c == 0)//如果计数器为0,则直接返回,因为计数器为0,则释放所有线程,CountDownLatch工作已经结束了
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))//原子性的将计数器减1,返回计数器是否==0
return nextc == 0;
}
}
2).当CountDownLatch调用await()时
//CountDownLatch调用Sync类中的acquireSharedInterruptibly(int i)方法,Sync中没有实现,所以是调用AQS的方法()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
AQS类
//调用子类的tryAcquireShared(int i)方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//判断线程是否中断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//结合下面的代码可以看出,当计数器不等于0时,往下执行,
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)//这段有点看不懂啊
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);//获取当前信号量是否为0,如果为0这个方法返回1,否则返回-1
if (r >= 0) {//当信号量为0的时候就退出死循环,否则一直在这里面出不去,所以实现了一直等待
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Sync类
//当计数器等于0时返回1,否则返回-1;
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
CountDownLatch示例
package com.synchronize;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 同步计数器
* @author lijh
*
*/
public class MyCountDownLatch {
public static void main(String[] args) {
CountDownLatch doneSignal = new CountDownLatch(5);
//定长线程池
ExecutorService pool = Executors.newFixedThreadPool(1);
for (int i = 0; i < 5; ++i){
pool.execute(new WorkerRunnable(doneSignal, i));
}
try {
//在计数器为0之前会一直等待
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//关闭线程池
pool.shutdown();
System.out.println("main 线程结束");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
//每个线程做完自己的事情后,计数器减1
doWork(i);
doneSignal.countDown();
Thread.currentThread().interrupt();
}
void doWork(int i) {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(doneSignal.getCount());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步计数器之--Semaphore
semaphore通常用于限制访问一些资源的线程数目,比如有一个服务器,同时最多只能三个人访问,此时可以使用semaphore实现
Semaphore方法比较多,此处展示一些常用的
Semaphore示例
package com.synchronize;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Semaphore同步器会规定一个阀值来限制对资源的访问,使用资源时,线程最多不能超出这个阀值
* 场景:当前驾校有2辆车,有5个学员练车,每次只有2个学员可以练车,
* 其他学员需要等待,当有学员练完车了,可以让给下一个学员练车
* @author lijh
*
*/
public class MySemaphore {
public static void main(String[] args) {
//最多两个线程访问资源
Semaphore sp = new Semaphore(2);
//创建一个容纳5个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
Driver d = new Driver(sp);
Car c1 = new Car(d);
Car c2 = new Car(d);
Car c3 = new Car(d);
Car c4 = new Car(d);
Car c5 = new Car(d);
pool.execute(c1);
pool.execute(c2);
pool.execute(c3);
pool.execute(c4);
pool.execute(c5);
pool.shutdown();//关闭线程池
}
}
//车
class Car implements Runnable{
private Driver d;
public Car(Driver d){
this.d = d;
}
@Override
public void run() {
d.driverCar();
}
}
//司机
class Driver{
//同步计数器
private Semaphore sp;
public Driver(Semaphore sp){
this.sp = sp;
}
//学员练车
public void driverCar(){
try {
sp.acquire();//从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
System.out.println(Thread.currentThread().getName()+" start");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" end");
sp.release();释放一个许可,将其返回给信号量。
//Thread.currentThread().interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步计数器之--CyclicBarrier
先看一个示例
package com.synchronize;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* CyclicBarrier 允许一组线程互相等待,直到到达某个公共屏障点,然后所有线程继续往后执行。
* CyclicBarrier 与 CountDownLatch 区别在于 CyclicBarrier 是多个线程互相等待,
* CountDownLatch 是一个线程等待多个线程完成工作,等待的对象不同了
* 场景:公司有4个人,每天早上开晨会时,大家一起去会议室,当所有人都到达会议室,会议开始。
*
* @author lijh
*
*/
public class MyCyclicBarrier {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cb = new CyclicBarrier(4,new Thread(new Meeting()));
ExecutorService pool = Executors.newFixedThreadPool(4);
Member s1 = new Member(cb);
Member s2 = new Member(cb);
Member s3 = new Member(cb);
Member s4 = new Member(cb);
pool.execute(s1);
pool.execute(s2);
pool.execute(s3);
//pool.execute(s4);
TimeUnit.SECONDS.sleep(5);
//当CyclicBarrier.await超时时,会抛出异常
pool.execute(s4);
pool.shutdown();
}
}
/**
* 成员
* @author lijh
*
*/
class Member implements Runnable{
private CyclicBarrier cb;
public Member(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("成员 "+Thread.currentThread().getName()+" 已经到达会议室!");
cb.await(3,TimeUnit.SECONDS);
//cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
/**
* 会议
* @author lijh
*
*/
class Meeting implements Runnable{
@Override
public void run() {
System.out.println("会议开始!");
}
}
CyclicBarrier有两个构造方法
CyclicBarrier(int i) 和CyclicBarrier(int i , Runnable r),第二个构造,是在所有参与者都在此屏障上后,会优先执行r这个线程,上面的示例也演示出了这一点。