代码改变世界

百度面试题解析:Zookeeper、ArrayList、生产者消费者模型及多线程(二) - 详解

2025-11-19 11:55  tlnshuju  阅读(0)  评论(0)    收藏  举报

#### 8. 谈一谈Zookeeper?

一个分布式协调服务,最初由Apache开发。它主导用于解决分布式系统中的协调困难,如配置管理、命名服务、同步服务和集群管理等。Zookeeper利用一个集中式的服务来管理分布式系统中的共享资源。就是Zookeeper

Zookeeper的核心功能包括:

- **分布式锁**:提供对共享资源的互斥访问。
- **Leader选举**:确保在多个节点之间选出一个领导者,避免冲突。
- **命名服务**:为分布式应用提供一致的命名服务。
- **配置管理**:献出分布式系统中统一的配置管理和更新机制。

Zookeeper的设计目标是:高可用、高性能、简单易用。

#### 9. Zookeeper如何实现分布式锁?

Zookeeper通过其强一致性的特性实现分布式锁。通常采用以下方式:

1. **节点创建**:客户端通过Zookeeper的`create`方法在Zookeeper中创建一个临时顺序节点(如 `/lock/lock-00001`)。这些节点的名称是有顺序的,可以自动分配编号。

2. **等待锁**:客户端在Zookeeper中列出所有锁节点,找到编号最小的一个。若是当前客户端创建的节点是最小的节点,则获取锁。

3. **释放锁**:客户端释放锁时,会删除自己的临时节点。其他客户端会通过Zookeeper的`watch`机制,监听该节点的变化。当某个节点被删除时,其他等待的客户端会获取到锁。

这样,每次客户端获取锁时,Zookeeper会确保锁的有序性和互斥性。Zookeeper的分布式锁通过临时节点和顺序节点实现了高效的同步和协作。

#### 10. Zookeeper的Leader选举过程

Zookeeper的Leader选举过程是经过节点的顺序号来实现的。常见的实现方式是利用Zookeeper的临时顺序节点机制。具体流程如下:

1. **客户端创建临时顺序节点**:每个参与选举的Zookeeper客户端都会在Zookeeper上创建一个临时顺序节点(例如:`/election/0001`、`/election/0002`等)。

2. **查找最小的节点**:每个参与者都会获取Zookeeper中的所有节点,查看自己创建的节点在所有节点中的顺序。

3. **判定Leader**:持有顺序号最小的节点就成为Leader。如果某个客户端的节点顺序号较大,它将等待最小顺序号节点的删除。

4. **节点删除与选举**:当Leader节点崩溃时,它在Zookeeper上的临时节点会被删除。其他候选节点会根据顺序号重新进行选举,确保系统始终存在一个Leader。

通过通过这个过程,Zookeeper能够在分布式环境中实现高效的Leader选举,保证系统的高可用性。

#### 11. Zookeeper的ZAB算法讲一下

ZAB(Zookeeper Atomic Broadcast)是Zookeeper的核心协议,旨在确保分布式系统中的数据一致性。ZAB协议的核心目标是保证在Zookeeper集群中,所有节点都能够同步并且达成一致。其工作过程包括以下几个阶段:

1. **选举阶段**:ZAB协议首先经过Leader选举来选择一个主节点(Leader)。只有Leader能够向其他节点广播数据。选举的过程通常是基于Zookeeper的顺序节点机制来实现的。

2. **广播阶段**:Leader节点将其事务日志(写操作)广播到所有Follower节点。这些数据传输是原子性的,并且所有Follower节点在收到Leader广播的资料后,都会进行持久化存储。

3. **确认阶段**:当Follower节点成功接收到Leader的广播数据并持久化后,它们会向Leader发送确认消息。只有当所有的Follower节点都确认了资料时,Leader才会认为数据已经提交。

4. **恢复阶段**:在Leader发生故障后,Zookeeper会根据ZAB协议进行Leader重新选举,保证数据一致性。

ZAB协议保证了Zookeeper的高可用性和数据一致性,是Zookeeper的核心设计之一。

#### 12. ArrayList和LinkedList的区别

Java中常用的列表类,但它们的实现方式和使用场景有所不同:就是`ArrayList`和`LinkedList`都

- **存储方式**:
- `ArrayList`底层是基于动态数组实现的,元素存储在连续的内存空间中。
- `LinkedList`底层是基于双向链表完成的,每个元素包含一个指向前一个和后一个元素的引用。

- **访问效率**:
- `ArrayList`支持快速的随机访问,由于它是基于数组实现的,访问某个元素的时间复杂度是O(1)。
- `LinkedList`需要从头或尾开始逐个遍历,访问某个元素的时间复杂度是O(n)。

- **插入和删除运行**:
- `ArrayList`在数组中间插入或删除元素时需要移动大量元素,时间复杂度为O(n)。
- `LinkedList`插入和删除元素只需调整指针,因此时间复杂度为O(1)。

- **内存使用**:
- `ArrayList`内存开销相对较小。
- `LinkedList`要求额外的内存存储前驱和后继节点的引用。

#### 13. ArrayList的add源码

`ArrayList`的`add`方法源码实现如下:

```java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount
elementData[size++] = e;
return true;
}
```

- **ensureCapacityInternal(size + 1)**:这个途径用来确保`ArrayList`的数组有足够的容量,如果容量不足,它会进行扩容。
- **elementData[size++] = e**:将元素添加到数组的末尾,并增加`size`。

#### 14. 讲一下生产者消费者模型(讲了三种方案)

生产者消费者模型用于解决多个线程之间共享数据的场景。通常有以下几种实现方案:

1. **阻塞队列(BlockingQueue)**:
- 使用`BlockingQueue`可以简单构建生产者消费者模型。生产者向队列中放入数据,消费者从队列中取数据。`BlockingQueue`会自动处理线程阻塞和唤醒机制。

2. **synchronized + wait/notify**:
- 利用`synchronized`关键字和`wait`/`notify`机制来达成。生产者在队列满时调用`wait`,消费者在队列为空时调用`wait`,当队列有数据或空间时,通过`notify`唤醒相应的线程。

3. **利用ReentrantLock**:
- 使用`ReentrantLock`代替`synchronized`,可以供应更灵活的锁机制。生产者和消费者利用锁和条件变量来控制数据的共享和线程的阻塞与唤醒。

#### 15. 生产者为什么用while?

在生产者消费者模型中,生产者使用`while`而不是`if`的原因是防止“虚假唤醒”。`wait()`会在没有满足条件的情况下返回,这种现象叫做虚假唤醒。使用`while`来循环检查条件,直到条件满足,才能继续执行。这样能确保线程在被唤醒后,仍然能够进行正确的判断。

```java
synchronized (queue) {
while (queue.size() == capacity) {
queue.wait();
}
queue.add(item);
queue.notifyAll();
}
```

经过`while`,即使`wait()`返回时队列没有空位,生产者也会继续等待,直到有空间添加新信息。