面试那些事(一)

最近裸辞了,就觉得解脱了好嗨哦!终于不要再看到领导丑恶的嘴脸!终于可以不要再逼着加班啦!终于周末可以好好的睡一觉了!

本来计划的是找好之后再离职。可是发现根本就没时间去准备!

每天的时间都被安排的满满的,下班回来都11-12点了。哪有心思去学习。。。

于是深思熟虑终于提出了离职。领导也很干脆,2天交接结束!

然后就是刷简历、面试不停循环 = =。

今天去面了一家互联网企业把遇到的一些问题都记下来ε=(´ο`*)))

1.讲一讲ConcurrentHashMap的底层实现

说到ConcurrentHashMap就不得不提到HashMap ,大家都知道HashMap是线程不安全的。

所以在高并发的情况下就会用到ConcurrentHashMap,那它是怎么解决线程安全问题的呢?

首先得知道它的核心是什么!

ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

大白话来讲就是分尸= = 这样大家就不用为了抢一个东西而斗争了。都有份  = =、

核心知道了就得知道它的结构是咋样?

这里写图片描述

 ConcurrentHashMap=Segment数组+table数组+HashEntry链表(对比 HashMap = table 数组+ node链表)

可能这样理解起来很费劲!你就联想HashMap的底层结构在外面又套了一层数组这样是不是就好多了!!

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {

    // 将整个hashmap分成几个小的map,每个segment都是一个锁;可以想象成外面大的一个hashMap数组
    // 这么设计的目的是对于put, remove等操作
    // 可以减少并发冲突,对不属于同一个片段的节点可以并发操作,大大提高了性能
    final Segment<K,V>[] segments;

    // segemnts数组的每一个元素就是一个HashMap  而一个hashMap由是由一个table数组和node节点组成
    //只不过在这里是HahsEnry数组和HahsEntry节点组成
    static final class Segment<K,V> extends ReentrantLock implements Serializable {
        transient volatile HashEntry<K,V>[] table;
        transient int count;
    }

    // 基本节点,存储Key, Value值
    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
    }
}

可是jvm的开发团队又觉得我搞个安全还要再外面套一层太low,能不能去掉外面一层实现同样的功能。

这一点我相信很多小伙伴都有同感有的时候实现一个功能事后自己看了觉得实现方式太low没有B格

于是苦思冥想终于想出了一个高大上的解决方案。之后还不忘自夸一下我他妈真是个天才!!

JDK1.8版本:做了2点修改。

1.取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作
2.将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构.

具体就不展开说了。。还要准备面试。明天继续面试(时间紧迫)

2.spring的Bean的加载过程

一样首先得了解bean加载的核心是什么

refresh()方法是bean加载的核心

它是ClassPathXmlApplicationContext的父类AbstractApplicationContext的一个方法,顾名思义,用于刷新整个Spring上下文信息,定义了整个Spring上下文加载的流程。

我理解就是spring上下文加载流程的总指挥!

源码如下

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            //准备刷新上下文
           //设置ConfigurableWebApplicationContext上下文实例对象wac的启动日期和活动标志、加载属性源配置以及判断必填项是否都完整。
            prepareRefresh();

            //获取刷新Spring上下文的Bean工厂
            // 如果已有就销毁,没有就创建;核心工作就是解析XML 以及扫描注解 将扫描到的Bean配置属性封装到BeanDefinition 对象中,并对它beanName(key) , BeanDefinition(v) 保存到一个Map 中。
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // 准备在上下文中使用这个bean工厂
            //bean factory进行一些上下文标准化配置:设置factory的类加载器、bean 表达式解释器、资源编辑注册器、应用上下文自动注入后处理器、注册可以自动装配的 Bean等
            prepareBeanFactory(beanFactory);

            try {
                // 修改BeanFactory的后处理器配置:在标准初始化之后修改应用程序上下文的内部bean工厂初始化配置,允许注册特殊BeanPostProcessors->即ServletContextAwareProcessor 后处理器以及设置自动装配忽略接口类
                postProcessBeanFactory(beanFactory);

                // 通过显示顺序方式调用手动注册的BeanFactory后处理器,先实例化spring框架涉及到的后处理器,在调用。
                invokeBeanFactoryPostProcessors(beanFactory);

                // 注册拦截bean创建的bean处理器-实例化
                registerBeanPostProcessors(beanFactory);

                // initMessageSource 初始化消息源
                initMessageSource();

                // 初始化事件广播器
                initApplicationEventMulticaster();

                // 初始化其他特定的bean
                onRefresh();

                // 注册监听事件:在容器中将所有项目里面的ApplicationListener注册进来
                registerListeners();

                // 初始化所有剩下的单实例 Bean(没有配置赖加载的 lazy!=true)
                finishBeanFactoryInitialization(beanFactory);

                // finishRefresh 完成BeanFactory的初始化创建工作
                finishRefresh();
            }

            catch (BeansException ex) {
                //销毁已经存在的单列避免浪费资源
                destroyBeans();

                // 重新设置active标志
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

真难翻译。。我吐了我当时没答上来。问这个问题再下输了。。。这里只是粗略的说了一下其中的一点具体更多。看来还是要对源码熟悉啊。

3.讲一讲spring的相互依赖的问题是如何解决的

参考这篇文章https://blog.csdn.net/w1lgy/article/details/81086171

4.讲一讲 redis的基本数据类型及各自场景

这个简单

4.1 String的应用场景

缓存
简单key-value存储

分布式锁
setnx key value,当key不存在时,将 key 的值设为 value ,返回1
若给定的 key 已经存在,则setnx不做任何动作,返回0。

当setnx返回1时,表示获取锁,做完操作以后del key,表示释放锁,如果setnx返回0表示获取锁失败,整体思路大概就是这样
如知乎每个问题的被浏览器次数

set key 0
incr key // incr readcount::{帖子id} 每阅读一次
get key // get readcount::{帖子id} 获取阅读量

4.2 hash的应用场景

redis的散列可以让用户将多个键值对存储到一个Redis的键里面,散列非常适用于将一些相关的数据存储在一起。类似map的一种结构,将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存到redis中,以后每次读写内存时,就可以操作hash里的某个字段

 

# 设置用户信息
hset student name test
hset student age 10

# 获取用户的所有相关信息
hgetall student
"name"
"test"
"age"
"10"

# 重新设置用户的年龄
hset student age 20

# 获取用户的年龄
hget student age
"20"

4.3 list 的应用场景

消息队列 先进先出  

栈  先进后出

# 实现方式一
lpush key value //一直往list左边放
brpop key value 10 
//key这个list有元素时,直接弹出,没有元素被阻塞,直到等待超时或发现可弹出元素为止,上面例子超时时间为10s

# 实现方式二
rpush key value
blpop key value 10

4.4 set的应用场景

无序集合,自动去重。

抽奖、点赞、签到

sadd key {userId} // 参加抽奖活动
smembers key //获取所有抽奖用户,大轮盘转起来
spop key count //抽取count名中奖者,并从抽奖活动中移除
srandmember key count //抽取count名中奖者,不从抽奖活动中移除



// 1001用户给8001帖子点赞
sadd like::8001 1001
srem like::8001 1001 //取消点赞
sismember like::8001 1001 //检查用户是否点过赞
smembers like::8001 //获取点赞的用户列表
scard like::8001 //获取点赞用户数

4.5 zset的应用场景

排序的set,可以去重还可以排序,写进去的时候给一个分数,自动根据根据分数排序,分数可以自定义排序规则

redis的zset天生是用来做排行榜的、好友列表, 去重, 历史记录等业务需求

// user1的用户分数为 10
zadd ranking 10 user1
zadd ranking 20 user2

// 取分数最高的3个用户
zrevrange ranking 0 2 withscores

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值