和信贷面试
HashMap的hash是如何计算的? 负载因子是什么?什么时候rehash的?
- hash的代码如下。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Java 左移运算 << ,丢弃最高位,0补最低位;带符号右移运算 >> 符号位不变,左边补上符号位;无符号右移运算 >>> 忽略了符号位扩展,0补最高位
^
是异或运算符。如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0
负载因子 loadFactor,默认值是0.75。 threshold = loadFactor * capacity。
-
什么时候rehash呢?
插入数据时,如果
transient Node<K,V>[] table
变量为null,则进行初始化。-
putVal成功之后,size代表当前HashMap中的元素个数。
if (++size > threshold) resize();
ConcurrentHashMap是如何实现多线程安全的呢?
- jdk1.8中,使用cas + sychornized 来实现线程安全。
ReentrantLock 的实现原理
- ReentrantLock 实现锁的底层原语是LockSupport.park()?unpark()?
- 在调用lock()方法是,会首先用cas乐观锁尝试获取锁,在获取锁失败的情况下会将当前线程封装成一个node存入一个双端队列。
- 在调用unlock()方法时,如果队列不为空,则会将队列中的下一个线程唤醒。
- 如果不使用双端队列,还有别的实现方式吗?这里回答的不太好,说官方的实现方式即是最优的实现方式,暂时想不到最优的实现方式。
分布式锁的实现方式
- redis setnx(key,value,expiretime)
- 实现原理
- zookeeper
- zookeeper在创建分布式锁时会在leader节点创建一个目录,当有 (n / 2) + 1 个节点返回ack时,锁即获取成功。
redis 实现原理 (技术总监面)
- redis 网络模型, 纯内存操作,高效的数据结构,数据备份方式四个主题一一详细的询问。
Mysql (技术总监面)
- MySQL 索引类型(B+Tree, Hash), B+tree是一个什么样的数据结构。
- 分布分表
- 分库分表策略
- 当分库分表达到数千个数据库时,如何进行汇总查询(要求在一个http request response timeout 内返回)。
- 我回答将查询同时发送到数千个实例,取到结果后本地内存merge , sort 。但是对此回答不满意,后来没有思路了,只能说在MySQL技术范畴之内我的技术储备能够解决的方法仅此一个了。
火花思维面试
手写代理模式(技术总监面)
dubbo 一次request发送完毕之后,如何获取相应结果的 (技术总监面,他说dubbo请求一个请求拿到结果之后下一个才能发送请求)
-
在 dubbo org.apache.dubbo.rpc.protocol.dubbo.ChannelWrappedInvoker#doInvoke 中可以看到
currentClient.request(inv).get()
这行请求的关键代码if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!"); } // create request. Request req = new Request(); req.setVersion(Version.getProtocolVersion()); req.setTwoWay(true); req.setData(request); DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout); try { channel.send(req); } catch (RemotingException e) { future.cancel(); throw e; } return future;
这行代码的调用如上,可以看到dubbo client 组装了请求参数之后,就通过 `
if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!"); } // create request. Request req = new Request(); req.setVersion(Version.getProtocolVersion()); req.setTwoWay(true); req.setData(request); DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout); try { channel.send(req); } catch (RemotingException e) { future.cancel(); throw e; } return future;
这行代码的调用如上,可以看到dubbo client 组装了请求参数之后,就通过`DefaultFuture.newFuture(channel, req, timeout)` 构造了一个DefaultFuture 。通过 channel将请求`send`即返回。这里并没有拿到response 。但是在返回`DefaultFuture`之后,在`dubbo org.apache.dubbo.rpc.protocol.dubbo.ChannelWrappedInvoker#doInvoke `中的 `currentClient.request(inv)`拿到`DefaultFuture`之后即调用`get`方法返回了RPC请求的结果。
```java
if (timeout <= 0) {
timeout = Constants.DEFAULT_TIMEOUT;
}
if (!isDone()) {
long start = System.currentTimeMillis();
lock.lock();
try {
while (!isDone()) {
done.await(timeout, TimeUnit.MILLISECONDS);
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
if (!isDone()) {
throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
}
}
return returnFromResponse();
```
get的实际逻辑如上,线程在请求参数发送出去之后即进入while循环中进行阻塞等待,通过设置阻塞等待的时间,如果超过指定时间服务端还没有相应结果则会跳出循环,抛出超时异常。如果在timeout时间范围内RPC响应结果返回,则 有如下代码
```java
private void doReceived(Response res) {
lock.lock();
try {
response = res;
done.signalAll();
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}
```
此时将会唤醒前面`get` 方法 while中的线程阻塞, 将结果返回。这就是一个请求响应的过程。
## redis 集群的几种实现方式(技术总监面)
> 这个问题回答的不是很好,只是简单的说是通过 redisClient 来实现的客户端分片,redis集群互相不感知。然后被问到当集群中有一台挂了会有什么样的结果时,就说到了一致性hash。当问挂了的数据对线上的操作造成影响时怎么办,然后回答说通过redis一主一丛。但是这个问题大体上来回回答的不是特别好。应当明确redis实现集群的几种方式(客户端分片,redis 原生cluster等),实现原理,`故障应对策略`等。
##