面试题答案
一键面试1. 原理差异
- Semaphore(信号量):维护了一组虚拟的许可(permits),线程要访问共享资源,首先要获取许可。如果没有许可,则线程被阻塞,直到有许可可用。
- ReentrantLock(可重入锁):基于AQS(AbstractQueuedSynchronizer)框架实现,通过一个FIFO队列来管理等待获取锁的线程。线程获取锁时,如果锁可用,则获取成功并将锁的持有计数加1;如果锁不可用,则线程进入等待队列。可重入意味着同一个线程可以多次获取同一个锁,每获取一次,持有计数增加1,释放时减少1,直到计数为0时真正释放锁。
- synchronized:是Java的关键字,基于Monitor机制实现。每个对象都有一个Monitor,线程进入同步块时会尝试获取对象的Monitor,如果获取成功则进入同步块,否则进入阻塞状态,等待Monitor的释放。synchronized也支持可重入,线程进入同步块时,Monitor的进入数加1,退出时减1。
- CountDownLatch:基于AQS实现,通过一个计数器来控制线程的等待和放行。线程调用
await()
方法时,如果计数器的值不为0,则线程进入等待状态;其他线程调用countDown()
方法将计数器减1,当计数器的值减为0时,所有等待的线程被唤醒。 - CyclicBarrier:同样基于AQS实现,它也有一个计数器,用于控制一组线程相互等待,直到所有线程都到达某个点(barrier)。线程调用
await()
方法时,计数器减1,当计数器减为0时,所有等待的线程被唤醒,并且计数器会重置为初始值,以便下次使用。
2. 功能差异
- Semaphore:主要用于控制同时访问特定资源的线程数量,比如数据库连接池,限制同时使用连接的线程数。
- ReentrantLock:与
synchronized
类似,用于实现线程同步,保证同一时间只有一个线程可以执行临界区代码。但ReentrantLock
提供了更灵活的锁获取和释放方式,如可中断的锁获取、公平锁等。 - synchronized:用于实现基本的线程同步,确保同一时间只有一个线程能够进入同步块。它可以作用于方法和代码块,并且在使用上相对简单。
- CountDownLatch:用于一个或多个线程等待其他一组线程完成操作后再继续执行,例如主线程等待所有子线程完成任务后再进行汇总统计。
- CyclicBarrier:用于一组线程相互等待,直到所有线程都准备好继续执行,比如多个线程并发处理数据,然后在某个点汇总结果。与
CountDownLatch
不同的是,CyclicBarrier
的计数器可以重置,可重复使用。
3. 性能差异
- Semaphore:性能取决于竞争的激烈程度,在高竞争环境下,由于频繁的许可获取和释放,可能会有一定的性能开销。
- ReentrantLock:在高竞争环境下,由于其基于AQS的灵活实现,性能可能优于
synchronized
。ReentrantLock
可以使用非公平锁来提高吞吐量,但可能导致某些线程长时间等待。 - synchronized:在Java 6及以后的版本中,通过各种优化(如偏向锁、轻量级锁等),性能有了很大提升。但在高竞争环境下,性能可能不如
ReentrantLock
。 - CountDownLatch:性能主要取决于
countDown()
操作的频率,在合理使用的情况下,性能开销较小。 - CyclicBarrier:性能同样取决于
await()
操作的频率,由于需要等待所有线程到达,在高并发场景下可能会有一定的性能瓶颈。
4. 适用场景差异
- Semaphore:适用于需要限制并发访问数量的场景,如资源池的管理。
- ReentrantLock:适用于需要更灵活控制锁获取和释放的场景,如可中断的锁获取、公平锁需求等。
- synchronized:适用于简单的线程同步场景,代码简洁,使用方便。
- CountDownLatch:适用于一个或多个线程等待其他一组线程完成任务的场景,如初始化等待。
- CyclicBarrier:适用于一组线程相互等待,共同完成某个阶段任务,然后继续下一个阶段的场景,如迭代计算。
5. 分布式系统中同步机制的选择及代码示例
5.1 资源池管理(Semaphore)
在分布式系统中,如果需要管理共享资源(如数据库连接、网络连接等)的并发访问数量,可以使用Semaphore
。
import java.util.concurrent.Semaphore;
public class DatabaseConnectionPool {
private static final int MAX_CONNECTIONS = 10;
private final Semaphore semaphore;
public DatabaseConnectionPool() {
semaphore = new Semaphore(MAX_CONNECTIONS);
}
public void getConnection() throws InterruptedException {
semaphore.acquire();
try {
// 获取数据库连接的逻辑
System.out.println("获取到数据库连接");
} finally {
semaphore.release();
System.out.println("释放数据库连接");
}
}
}
5.2 简单线程同步(synchronized)
如果分布式系统中的某个模块需要简单的线程同步,确保同一时间只有一个线程执行临界区代码,可以使用synchronized
。
public class SimpleCounter {
private int count = 0;
public synchronized void increment() {
count++;
System.out.println("Count: " + count);
}
}
5.3 灵活锁控制(ReentrantLock)
当需要更灵活的锁控制,如可中断的锁获取时,可以使用ReentrantLock
。
import java.util.concurrent.locks.ReentrantLock;
public class FlexibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() {
try {
if (lock.tryLock()) {
try {
// 临界区代码
System.out.println("获取到锁,执行任务");
} finally {
lock.unlock();
}
} else {
System.out.println("无法获取锁,放弃任务");
}
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
}
5.4 线程等待一组任务完成(CountDownLatch)
在分布式系统中,如果某个模块需要等待其他多个任务完成后再继续执行,可以使用CountDownLatch
。
import java.util.concurrent.CountDownLatch;
public class TaskCompletionExample {
public static void main(String[] args) {
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep(1000);
System.out.println("任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
try {
latch.await();
System.out.println("所有任务完成,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.5 多线程相互等待(CyclicBarrier)
如果分布式系统中的多个线程需要相互等待,共同完成某个阶段任务后再继续下一个阶段,可以使用CyclicBarrier
。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("所有线程到达屏障,开始下一阶段");
});
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println("线程准备就绪");
barrier.await();
// 下一阶段的任务
System.out.println("线程继续执行下一阶段任务");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}