概述
juc是java的核心包,而juc下面的核心包之一应该就是locks了,而locks下最核心的类就是AbstractQueuedSynchronizer类(简称aqs),它可以说是多线程下所有锁相关类的父类,我们来看看它有多少子类。
可以看到有ReentrantLock,ReentrantReadWriteLock,Semaphore,CountDownLatch,ThreadPoolExecutor。ps: CyclicBarrier是采用ReentrantLock实现的。这次我们先学习一下多线程下计数器的相关使用,分别是CountDownLatch,CyclicBarrier,Semaphore。
CountDownLatch
作用:计数器
应用场景:
- 在主线程中等待所有线程完成之后做统计汇总(eg.开启若干个线程下载文件,需等待所有文件下载结束之后统计总下载文件大小)
- 所有线程等待主线程通知,通知后所有线程开始执行任务(eg.开启若干个线程下载文件,但是所有线程必须同时开始执行下载任务)
代码1:
public class TestCountDownLatch {
// 请自行设置图片地址
public static String[] targetUrls = new String[]{
"http://xxxx/download/attachments/25559453/%E6%8A%B5%E6%89%A3%E5%88%B8.JPEG?version=1&modificationDate=1606189758000&api=v2",
"http://xxxx/download/attachments/25562400/%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6.png?version=1&modificationDate=1608003595000&api=v2",
"http://xxxx/download/attachments/15772822/image2020-6-15_18-16-16.png?version=1&modificationDate=1592216176000&api=v2",
"http://xxxx/download/attachments/6675013/image2019-3-28%2016%3A4%3A35.png?version=1&modificationDate=1553760442000&api=v2",
"http://xxxx/download/attachments/25562935/image2020-12-18_10-37-43.png?version=1&modificationDate=1608259064000&api=v2"
};
public static void main(String[] args) {
TestCountDownLatch testCountDownLatch = new TestCountDownLatch();
testCountDownLatch.downloadFiles();
}
/**
* 使用多线程下载5张图片,并在所有文件下载完毕之后统计文件大小
*/
private void downloadFiles() {
CountDownLatch countDownLatch = new CountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<Long>> futures = new ArrayList<>();
for (String targetUrl : targetUrls) {
// submit和execute的区别:submit可以直接返回future结果,而execute必须将结果作为变量传入
futures.add(executorService.submit(new DownLoadTask(countDownLatch, targetUrl)));
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
long sum = 0;
for (Future<Long> future : futures) {
try {
sum += future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("下载文件总大小:" + sum);
}
public static long downloadFile(String mUrl, String fileName) throws MalformedURLException {
// 下载网络文件
int byteSum = 0;
int byteRead = 0;
URL url = new URL(mUrl);
try {
URLConnection conn = url.openConnection();
InputStream inStream = conn.getInputStream();
FileOutputStream fs = new FileOutputStream("D:/" + fileName + ".png");
byte[] buffer = new byte[1204];
while ((byteRead = inStream.read(buffer)) != -1) {
byteSum += byteRead;
fs.write(buffer, 0, byteRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println(fileName + " size : " + byteSum);
}
return byteSum;
}
static class DownLoadTask implements Callable<Long> {
private CountDownLatch countDownLatch;
private String url;
public DownLoadTask(CountDownLatch countDownLatch, String url) {
this.countDownLatch = countDownLatch;
this.url = url;
}
@Override
public Long call() throws Exception {
long sum = downloadFile(url, Thread.currentThread().getName());
countDownLatch.countDown();
return sum;
}
}
执行结果:
pool-1-thread-3 size : 34689
pool-1-thread-2 size : 34711
pool-1-thread-1 size : 34694
pool-1-thread-4 size : 34693
pool-1-thread-5 size : 34690
下载文件总大小:173477
代码2:
/**
* 开启5个线程下载文件,但必须同一时间开始。
* 准点抢购等场景
*/
private void downloadFilesSameTime() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (String url : targetUrls) {
executorService.execute(new DownLoadTaskSameTime(countDownLatch, url));
}
System.out.println("开始下载");
countDownLatch.countDown();
}
static class DownLoadTaskSameTime implements Runnable {
private String url;
private CountDownLatch countDownLatch;
public DownLoadTaskSameTime(CountDownLatch countDownLatch, String url) {
this.countDownLatch = countDownLatch;
this.url = url;
}
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
String treadName = Thread.currentThread().getName();
System.out.println(treadName + "开始下载");
downloadFile(url, treadName);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
执行结果:
开始下载
pool-1-thread-1开始下载
pool-1-thread-5开始下载
pool-1-thread-4开始下载
pool-1-thread-2开始下载
pool-1-thread-3开始下载
pool-1-thread-4 size : 34693
pool-1-thread-5 size : 34690
pool-1-thread-1 size : 34694
pool-1-thread-3 size : 34689
pool-1-thread-2 size : 34711
CyclicBarrier
作用:计数器
应用场景:cyclic是循环,barrier是栅栏,合起来就是可循环的栅栏。多线程执行任务,必须等到所有线程到达某个点之后,线程才可以继续执行。举例:军队中军人集合执行任务。每个人相当于一个线程,所有人必须全部到达集合点之后,才可以执行任务。
代码:
public static void main(String[] args) {
TestCyclicBarrier testCyclicBarrier = new TestCyclicBarrier();
testCyclicBarrier.testCyclicBarrier();
}
private void testCyclicBarrier() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("所有线程文件都下载结束了,各自展示文件大小吧");
}
});
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (String targetUrl : TestCountDownLatch.targetUrls) {
// submit和execute的区别:submit可以直接返回future结果,而execute必须将结果作为变量传入
executorService.submit(new DownloadFileTask(cyclicBarrier, targetUrl));
}
}
static class DownloadFileTask implements Runnable {
private CyclicBarrier cyclicBarrier;
private String url;
public DownloadFileTask(CyclicBarrier cyclicBarrier, String url) {
this.cyclicBarrier = cyclicBarrier;
this.url = url;
}
@Override
public void run() {
long sum = 0;
try {
sum = TestCountDownLatch.downloadFile(url, Thread.currentThread().getName());
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(sum);
}
}
执行结果:
pool-1-thread-1 size : 34694
pool-1-thread-3 size : 34689
pool-1-thread-5 size : 34690
pool-1-thread-2 size : 34711
pool-1-thread-4 size : 34693
所有线程文件都下载结束了,各自展示文件大小吧
34693
34694
34689
34690
34711
CyclicBarrier和CountDownLatch的区别
两者都可以作为多线程下的计数器,应用场景有一些小区别,将创建CountDownLatch的线程设为主线程的话,可以在主线程汇总其他线程返回的数据,或者控制其他线程同时执行;CyclicBarrier作用都是在其他线程,可以控制所有线程在某个点等待其他线程全部执行到该点后,所有线程继续执行。
此外CountDownLatch是不可重复使用的,而CyclicBarrier是可以重复使用的,调用reset()即可。
两者都是递减计数的。
Semaphore
作用:信号量,控制同时访问资源的数量
应用场景:应用在资源有明确访问数量的场景。停车场有固定数量的车位,在进入一辆车之后,车位数减1,出去一辆车,数量加1,当车位满了之后,车辆禁止进入;固定线程数的线程池,任何时候最多只有固定数量的线程在执行任务。
代码:
public static void main(String[] args) {
TestSemaphore testSemaphore = new TestSemaphore();
testSemaphore.downloadFilesWithSemaphore();
}
/**
* 仍然是下载五张图片,虽然线程池数量是5
* 但是我希望同时最多三个文件在下载
*/
private void downloadFilesWithSemaphore() {
Semaphore semaphore = new Semaphore(3, true);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (String url : TestCountDownLatch.targetUrls) {
executorService.execute(new DownloadFileTask(semaphore, url));
}
}
static class DownloadFileTask implements Runnable {
private Semaphore semaphore;
private String url;
public DownloadFileTask(Semaphore semaphore, String url) {
this.semaphore = semaphore;
this.url = url;
}
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "开始下载");
TestCountDownLatch.downloadFile(url, threadName);
System.out.println(threadName + "完成下载");
} catch (MalformedURLException e) {
e.printStackTrace();
}
semaphore.release();
}
}
执行结果:
pool-1-thread-1开始下载
pool-1-thread-2开始下载
pool-1-thread-5开始下载
pool-1-thread-2 size : 34711
pool-1-thread-5 size : 34690
pool-1-thread-5完成下载
pool-1-thread-1 size : 34694
pool-1-thread-1完成下载
pool-1-thread-2完成下载
pool-1-thread-3开始下载
pool-1-thread-4开始下载
pool-1-thread-3 size : 34689
pool-1-thread-3完成下载
pool-1-thread-4 size : 34693
pool-1-thread-4完成下载
总结
CountDownLatch:1. 主线程调用await()等待,所有子线程执行完成后调用countdown()后,计数器到0之后回调,做统计汇总;2. 所有子线程调用await()等待,主线程调用countdown()方法,子线程全部开始执行任务。
CyclicBarrier:所有子线程有两个任务A和B需要完成,所有子线程执行B任务前必须等待所有子线程都完成A任务。子线程执行A任务后,调用await(),先完成A任务的线程需等待其他线程完成A任务,全部完成后回调,所有子线程可继续执行B任务。CyclicBarrier可重置后继续使用,CountdownLatch不可以。
Semaphore:针对一个共享资源,控制访问其的线程数量(类似于停车场车位是共享资源,控制进入停车场的数量)。支持公平锁和非公平锁。