我们先说AQS,全程是AbstractQueueSynchronizer,顾名思义,抽象的队列式的同步器,AQS定义了一套多个线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等等。
这次我们重点说一下Semaphore、CountDownLatch、CyclicBarrier。首先看一下CountDownLatch,countDownLatch来保证所有线程最后同步计算某个任务,如果先执行的线程会等待其他线程,其中countDown()方法来执行减1操作,await()方法来执行等待操作。
下面我们用java代码来模拟多线程情况的使用countDownLatch来同步线程。
/**
* countDownLatch来保证所有线程最后同步计算某个任务,先执行的线程会等待其他线程。
*/
@Slf4j
public class CountDownLatchExample1 {
//一同并发执行的线程数量
private final static int threadCount = 20;
public static void main(String[] args) throws InterruptedException {
//定义一个线程池
ExecutorService exec = Executors.newCachedThreadPool();
//定义一个闭锁
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
test(threadNum);
}catch (Exception e) {
log.error("exception",e);
}finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
log.info("finish");
exec.shutdown();
}
public static void test(int threadNum) throws Exception{
Thread.sleep(100);
log.info("{}",threadNum);
Thread.sleep(100);
}
}
我们来看一下打印结果
我们可以看到当这20个线程都执行完毕后,最后才打印出finish,因为其中一个线程执行完test方法后走到await()方法,会等待其他线程,而且会先执行减1操作,最后为0,继续下一步的执行。
await()方法里也可以有参数,参数的含义为超时时间,就是说在这些时间里,能执行多少线程就执行多少,超出时间的线程就丢弃,不管了。
countDownLatch.await(10, TimeUnit.MILLISECONDS);//等待10ms,超过10ms后,将不会等待其他线程。
我们再看Semaphore,刚刚的countDownLatch是来保证线程同步执行,而Semaphore是来控制并发线程的数量,想让这一时刻3个线程并发则Semaphore中参数设为3,有3个资源。代码如下
/**
*用semaphore来控制并发数量
*/
@Slf4j
public class SemaphoreExample1 {
private final static int threadCount = 20;
public static void main(String[] args) throws InterruptedException {
//定义一个线程池
ExecutorService exec = Executors.newCachedThreadPool();
//定义一个信号量
final Semaphore semaphore=new Semaphore(3);//允许的并发数
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire();//获得一个信号量资源
test(threadNum);
semaphore.release();//释放资源
}catch (Exception e) {
log.error("exception",e);
}
});
}
log.info("finish");
exec.shutdown();
}
public static void test(int threadNum) throws Exception{
log.info("{}",threadNum);
Thread.sleep(1000);
}
}
执行结果是3个线程每隔1秒打印出来,大家可以再控制台测试查看效果。
最后我们来说一下CyclicBarrier,他和CountDownLatch类似,都是控制线程同步的,只不过CyclicBarrier是每个线程之间的互相等待,最后都执行好了进行下一步,CountDownLatch则是一个线程或多个线程等待一个线程执行完毕后,执行下一步。
我们直接看代码来区别他们
@Slf4j
public class CyclicBarrierExample1 {
private static CyclicBarrier barrier = new CyclicBarrier(5);//5个线程要同步等待
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 10; i++) {
final int threadNum = i;
Thread.sleep(1000);
exec.execute(() -> {
try {
race(threadNum);
}catch (Exception e) {
log.error("exception",e);
}
});
}
}
public static void race(int threadNum) throws Exception{
Thread.sleep(100);
log.info("{} is ready",threadNum);
barrier.await();
log.info("{} is continue",threadNum);
}
}
我们发现await()方法的位置并不一样,一个在动作体之内,一个在动作之外。
看一下打印结果
因为我们给CyclicBarrier设置的参数为5,即5个线程才能共同进行下一步,发现打印出来的5个准备好之后,才进行下一步。
当然,CyclicBarrier和Semaphore也可以设置他们的超时时间,用法类似。