每日三个JAVA经典面试题(十六)

1.AQS 对资源的共享方式?

AbstractQueuedSynchronizer(AQS)是Java并发包中的一个关键框架,用于构建锁和其他同步器。AQS提供了对资源共享方式的支持,主要分为两种模式:独占模式共享模式。这两种模式决定了同步状态(即资源)的获取和释放方式。

独占模式(Exclusive Mode)

独占模式意味着每次只有一个线程可以获取同步状态。这种模式适用于实现互斥锁等同步器,例如ReentrantLock就是基于AQS的独占模式实现的。在独占模式下,尝试获取同步状态的线程,如果成功,则该线程独占资源直到它释放同步状态;如果失败,则该线程会被加入到AQS的等待队列中,等待其他线程释放锁后再次尝试。

独占模式的核心方法包括:

  • tryAcquire(int arg):尝试获取资源。子类需要根据资源的状态决定如何实现这个方法。
  • tryRelease(int arg):尝试释放资源,返回值表示资源是否完全释放。

共享模式(Shared Mode)

共享模式允许多个线程同时获取同步状态。这种模式适用于实现如Semaphore(信号量)、CountDownLatch(倒计时门闩)等同步器。在共享模式下,资源可以被多个线程共享,例如,信号量允许多个线程同时访问一个有限的资源池。

共享模式的核心方法包括:

  • tryAcquireShared(int arg):尝试以共享方式获取资源。该方法返回值表示是否成功获取以及是否有其他线程可以继续获取资源。
  • tryReleaseShared(int arg):尝试释放资源。返回值表示当前状态下其他线程是否可以继续获取资源。

模式的选择

AQS通过提供tryAcquiretryReleasetryAcquireSharedtryReleaseShared等方法,允许基于AQS的同步器根据具体需求选择独占模式或共享模式。同步器的实现者可以根据资源的特性和线程对资源的访问规则选择合适的模式。

总结

AQS提供了一套灵活的机制来支持不同的资源共享方式,让Java并发包中的同步器能够有效地管理线程对资源的访问。通过独占模式和共享模式的区分,AQS能够适用于广泛的并发场景,实现从基本的互斥锁到复杂的协调器等多种同步需求。

2.如何让 Java 的线程彼此同步?

在Java中,有多种方式可以实现线程之间的同步,以确保它们在访问共享资源时不会产生冲突。以下是一些常用的线程同步方法:

1. 使用synchronized关键字

synchronized关键字可以用于方法或代码块,确保每次只有一个线程可以执行synchronized标记的代码段。

  • 同步方法:将整个方法声明为同步。
    public synchronized void method() {
        // 同步代码
    }
    
  • 同步代码块:指定一个对象作为锁,只有获取这个锁的线程才能执行代码块。
    public void method() {
        synchronized(this) {
            // 同步代码
        }
    }
    

2. 使用ReentrantLock

ReentrantLockjava.util.concurrent.locks包中的一个类,提供了比synchronized更灵活的锁定机制。它需要显式地获取和释放锁。

ReentrantLock lock = new ReentrantLock();
public void method() {
    lock.lock();  // 获取锁
    try {
        // 同步代码
    } finally {
        lock.unlock();  // 释放锁
    }
}

3. 使用volatile关键字

volatile关键字确保变量的修改对所有线程立即可见,用于轻量级的同步场景(主要是可见性,不保证原子性)。

private volatile boolean running = true;
public void method() {
    while(running) {
        // 执行任务
    }
}

4. 使用Atomic

Java的java.util.concurrent.atomic包提供了一系列原子类(如AtomicIntegerAtomicLong等),它们利用CAS(Compare-And-Swap)操作提供了非阻塞的原子性操作,适用于计数器或累加器等简单同步需求。

private AtomicInteger count = new AtomicInteger();
public void increment() {
    count.incrementAndGet();  // 原子性操作
}

5. 使用高级并发控制工具

Java的java.util.concurrent包提供了许多高级并发控制工具,如CyclicBarrierCountDownLatchSemaphorePhaser等,用于协调多个线程之间的同步。

CountDownLatch latch = new CountDownLatch(1);
public void method() {
    latch.await();  // 等待直到latch减到0
    // 执行任务
}
public void release() {
    latch.countDown();  // 将latch的计数减1
}

选择合适的同步机制取决于具体的应用场景和性能需求。在设计多线程程序时,应该尽量减少锁的范围和持有时间,以避免性能瓶颈或死锁。

3.你了解过哪些同步器?请分别介绍下。

Java的java.util.concurrent包提供了一系列强大的同步器,用于协调多线程之间的交云互动。这些同步器包括但不限于CountDownLatchCyclicBarrierSemaphorePhaser,它们各自有不同的用途和工作原理。

1. CountDownLatch

CountDownLatch是一个同步辅助类,用于使一个或多个线程等待一组事件发生。构造时传入一个计数值,该计数值表示需要等待的事件数量。CountDownLatch提供了countDown()方法来表示一个事件已经发生(每调用一次计数值减1),以及await()方法来等待所有事件完成(即计数值达到0)。

用途CountDownLatch常用于等待服务的初始化完成,或等待某些操作的完成,才开始执行后续的操作。

2. CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程相互等待,直到所有线程都达到一个共同的屏障点(Barrier point)再继续执行。CyclicBarrier可以重复使用,当所有等待线程都被释放后,屏障被重置,可以再次用于新的一组等待线程。

用途CyclicBarrier适用于分步处理任务的场景,当所有线程完成第一步操作后,再一起开始执行第二步操作。

3. Semaphore

Semaphore是基于计数的同步辅助类,它维护了一个许可集。Semaphore可以控制对某组资源的访问权限。线程可以通过调用acquire()方法获取许可,如果没有可用的许可,acquire()将阻塞直到有许可成为可用。线程完成资源使用后,调用release()方法来释放许可。

用途Semaphore适用于资源池限制访问或限流场景,如数据库连接池。

4. Phaser

Phaser是一个灵活的同步辅助类,它可以替代CountDownLatchCyclicBarrierPhaser提供了对动态数量的参与者线程进行同步的能力,允许线程在执行过程中动态地注册和注销。它支持分阶段(phase)的同步,每个阶段都可以有不同数量的参与者。

用途Phaser适用于复杂的并发控制场景,如多阶段任务的协调执行,其中参与者数量可能会变化。

总结

这些同步器提供了强大的线程同步机制,帮助开发者在多线程程序中协调线程的执行,控制并发访问,以及管理复杂的同步需求。选择哪个同步器取决于具体的应用场景和所需的同步特性。

你可能感兴趣的:(java,面试,开发语言)