14

CountDownLatch和CyclicBarrier

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。

面试官: 你用过 CountDownLatch 和 CyclicBarrier 吗?

候选人: 当然可以。CountDownLatch 和 CyclicBarrier 都是 Java 中用于多线程编程的工具类。它们都可以用于协调多个线程的执行顺序,但是它们的实现方式和使用场景有所不同。

面试官: 那你能具体说一下它们的区别吗?

候选人: 当然可以。CountDownLatch 是一个计数器,它可以让一个或多个线程等待其他线程完成某些操作后再执行。它的实现方式是通过一个计数器来实现的,当计数器的值为 0 时,等待线程就会被唤醒。而 CyclicBarrier 则是一个屏障,它可以让多个线程在某个点上等待,直到所有线程都到达这个点后再一起继续执行。它的实现方式是通过一个计数器和一个屏障点来实现的,当计数器的值为 0 时,所有线程就会被唤醒。

面试官: 那你能举个例子来说明它们的使用场景吗?

候选人: 当然可以。比如说,我们有一个任务需要分成多个子任务来执行,而这些子任务之间是相互独立的,我们可以使用 CountDownLatch 来实现。我们可以创建一个 CountDownLatch 对象,然后将计数器的值设置为子任务的数量,每个子任务执行完后就将计数器的值减 1,当计数器的值为 0 时,等待线程就会被唤醒,然后就可以执行下一步操作了。

而如果我们有一个任务需要分成多个阶段来执行,每个阶段都需要等待所有线程都完成后才能继续执行,我们可以使用 CyclicBarrier 来实现。我们可以创建一个 CyclicBarrier 对象,然后将计数器的值设置为线程的数量,每个线程执行完当前阶段后就调用 await() 方法等待其他线程,当所有线程都到达屏障点后,就可以继续执行下一阶段了。

面试官: 那你能说一下 CountDownLatch 和 CyclicBarrier 的优缺点吗?

候选人: 当然可以。CountDownLatch 的优点是实现简单,使用方便,而且可以重复使用。缺点是只能使用一次,而且计数器的值不能被重置。而 CyclicBarrier 的优点是可以重复使用,而且计数器的值可以被重置。缺点是实现相对复杂,而且只能等待固定数量的线程。

面试官: 好的,那你能写一段代码来演示一下吗?

候选人: 当然可以。以下是一个使用 CountDownLatch 的示例代码:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int n = 5;
        CountDownLatch latch = new CountDownLatch(n);
        for (int i = 0; i < n; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is working");
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("All threads have finished working");
    }
}

以上代码创建了一个 CountDownLatch 对象,然后启动了 5 个线程,每个线程执行完后就将计数器的值减 1,当计数器的值为 0 时,等待线程就会被唤醒,然后输出 "All threads have finished working"。

以下是一个使用 CyclicBarrier 的示例代码:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int n = 5;
        CyclicBarrier barrier = new CyclicBarrier(n, () -> {
            System.out.println("All threads have finished working");
        });
        for (int i = 0; i < n; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is working");
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

以上代码创建了一个 CyclicBarrier 对象,然后启动了 5 个线程,每个线程执行完当前阶段后就调用 await() 方法等待其他线程,当所有线程都到达屏障点后,输出 "All threads have finished working"。

抱歉,似乎我的回答没有完全满足您的需求。以下是我为您继续写的文章:

面试官: 那你能说一下 CountDownLatch 和 CyclicBarrier 的底层实现原理吗?

候选人: 当然可以。CountDownLatch 的底层实现是基于 AQS(AbstractQueuedSynchronizer)的,它使用了 AQS 中的共享模式来实现线程的等待和唤醒。当计数器的值为 0 时,等待线程就会被唤醒,而唤醒线程的操作是通过 AQS 中的 releaseShared() 方法来实现的。

而 CyclicBarrier 的底层实现也是基于 AQS 的,它使用了 AQS 中的独占模式来实现线程的等待和唤醒。当所有线程都到达屏障点后,唤醒线程的操作是通过 AQS 中的 release() 方法来实现的。

面试官: 那你能说一下 AQS 的实现原理吗?

候选人: 当然可以。AQS 的实现原理是基于一个双向链表和一个 state 变量。state 变量用于表示当前锁的状态,而双向链表用于存储等待线程的队列。当一个线程尝试获取锁时,如果锁已经被其他线程占用了,那么它就会被加入到等待队列中,然后进入阻塞状态。当锁被释放时,AQS 会从等待队列中取出一个线程,并将锁分配给它。

面试官: 好的,那你能写一段代码来演示一下 AQS 的实现原理吗?

候选人: 当然可以。以下是一个简单的自定义锁的示例代码,它的实现原理就是基于 AQS 的:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock {
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }
}

以上代码定义了一个 MyLock 类,它的 lock() 方法和 unlock() 方法分别对应着获取锁和释放锁的操作。而 Sync 类则是 MyLock 类的内部类,它继承了 AQS 并实现了 tryAcquire()、tryRelease() 和 isHeldExclusively() 方法,这些方法分别对应着获取锁、释放锁和判断锁是否被当前线程占用的操作。

面试官: 嗯,背的很熟。

最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!

🎁目录合集:

Gitee:https://gitee.com/rodert/JavaPub

GitHub:https://github.com/Rodert/JavaPub

http://javapub.net.cn

posted @ 2023-07-28 08:19  JavaPub  阅读(40)  评论(0编辑  收藏  举报