文章目录
更多关于Java并发编程的文章请点击这里:Java并发编程实践(0)-目录页
本文将介绍什么是闭锁,在java中的闭锁实现:CountDownLatch类及其常用方法等,最后给出了一个使用闭锁模拟线程并发的demo,用以简单地测试任务是否为线程安全。
一、什么是闭锁
闭锁(Latch)是在并发编程中常被提及的概念。闭锁是一种线程控制对象,它能让所有的线程在某个状态时终止工作并等待,直到闭锁“开门”时,所有的线程在这一刻会几乎同时执行工作,制造出一个并发的环境。
二、CountDownLatch类介绍
2.1、什么是CountDownLatch
CountDownLatch,顾名思义,可以理解为计数(count)、减少(down)、闭锁(Latch),即通过计数减少的方式来达到阻碍线程执行任务的一种闭锁,这个类位于java.util.concurent并发包下,是java中闭锁的最优实现。
2.2、构造方法
CountDownLatch(int count)
构造器中计数值(count)就是闭锁需要等待的线程数量,这个值只能被设置一次。
2.3、主要方法
-
void await():
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 -
boolean await(long timeout, TimeUnit unit):
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 -
void countDown():
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 -
long getCount():
返回当前计数。 -
String toString():
返回标识此锁存器及其状态的字符串。
三、闭锁的使用
1.一个计数器使用的例子:主线程等待其他所有线程执行完毕在执行
public class CountDownLatchDemo {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
0, // corePoolSize
Integer.MAX_VALUE, // maxPoolSize
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()
);
int count = 5;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
threadPoolExecutor.execute(() -> {
try {
run();
} catch (Exception e) {
}finally {
latch.countDown();
}
});
}
latch.await();
threadPoolExecutor.shutdown();
System.out.println("线程执行完毕");
}
private static void run() throws InterruptedException {
Thread.sleep(1000);
System.out.println(Thread.currentThread());
}
}
2.一个并发测试的例子:将所有的子线程全部阻塞在一个入口,等待主线程统一放行
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(1);
int i = 0;
for (i = 0; i <= 50; i++) {
final int num = i;
executorService.submit(()->{
try {
countDownLatch.await();
System.out.println(String.format("线程%s打印%d", Thread.currentThread().getName(), num));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
Thread.sleep(5000);
System.out.println("FINISHED");
countDownLatch.countDown();
executorService.shutdown();
}
可以看到执行结果中,counter的值出现了并发冲突:
这时可以使用原子类AtomicInteger,通过CAS去自增计数器的值(为什么不使用volatile呢?因为volatile虽然能保证多线程读写共享变量的可见性,但修改一个变量并写入毕竟是分为读-改-写这三个过程的,所以仍需要使用CAS来保障)。
private static AtomicInteger counter = new AtomicInteger();
private static void run() throws InterruptedException {
// 使用原子类 CAS更新值
System.out.println(Thread.currentThread() + ":" + counter.incrementAndGet());
}