面试(复盘)

目录

医渡云

2022-03-04 (50min)

1. 项目使用的redis存的什么类型的数据?

答:存的是json序列化的数据。(String)
使用SpringCache 结合redis 实现对方法返回对象的缓存。
编写RedisConfig类 继承 CachingConfigurerSupport类。
重写cacheManager方法。

@Configuration
public class RedisConfig extends CachingConfigurerSupport{
/**
     * 选择redis作为默认缓存工具
     * *SpringBoot2.0以上CacheManager配置方式
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
    RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                // 设置key为String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
                // 设置value 为自动转Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
                // 不缓存null
                .disableCachingNullValues()
                // 缓存数据保存1小时
                .entryTtl(Duration.ofHours(1));

        RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder
                // Redis 连接工厂
                .fromConnectionFactory(redisTemplate.getConnectionFactory())
                // 缓存配置
                .cacheDefaults(defaultCacheConfiguration)
                // 配置同步修改或删除 put/evict
                .transactionAware()
                .build();
        return redisCacheManager;
    }
}

同时为redistmplate配置了一个bean,设置序列化与反序列化方法

@Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

2.token里面都放了哪些信息,怎么生成及判断?

首先对验证码进行验证

@PostMapping("/login")
    @IpRequired
    public Result login(@RequestBody UserVo userVo, HttpServletRequest request) {
        User user = new User();
        BeanUtils.copyProperties(userVo, user);
        // 验证如果不通过,后台直接抛异常
        userService.verifyCode(userVo.getVerKey(), userVo.getCode(),
                (String) redisUtil.get(userVo.getVerKey()));
    }

@Override
    public boolean verifyCode(String verKey, String code, String realCode) throws RuntimeException {
        // 验证码是否正确都删除,否则验证错误的验证码会存在redis中无法删除
        redisUtil.del(verKey);
        if (realCode == null || StringUtils.isEmpty(realCode)) {
            throw new RuntimeException("请输入验证码!");
        }
        if (!code.equalsIgnoreCase(realCode)) {
            throw new RuntimeException("请输入正确的验证码!");
        }
        return true;
    }

然后根据用户名跟密码判断用户是否匹配

 @Override
    public User login(User user) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("uid", "username", "password", "data_status", "nickname", "avatar");
        wrapper.eq("username", user.getUsername());
        //登录的用户
        User login_user = userDao.selectOne(wrapper);
        log.debug("login_user:[{}]", login_user.toString());
        if (!encoder.matches(user.getPassword(), login_user.getPassword())) {
            throw new RuntimeException("用户名或密码不正确,登录失败");
        }
        if (login_user.isDataStatus() == (MessageConstant.UserDisable)) {
            throw new RuntimeException("用户已被禁用,登录失败");
        }
 }

将用户id与用户名放入payload,使用JWTUtils生成token

HashMap<String, String> payload = new HashMap<>();
            payload.put("id", String.valueOf(userDB.getUid()));
            payload.put("lastIp", userDB.getLastIp());
            payload.put("username", userDB.getUsername());
            String token = JWTUtils.getToken(payload);
            
private static final String SING = "!@*(^*#sfdf&*$asdh$F&^";
public static String getToken(Map<String, String> map) {
        //设置过期时间
        Calendar instance = Calendar.getInstance();
        //默认七天过期
        instance.add(Calendar.DATE, 7);
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload键值
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        //令牌过期时间
        String token =builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SING));//签名
        return token;
    }

Token组成:header.payload.signature(头.负载.签名)
签名的过程,是对头部以及负载内容进行签名,如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务端会判断出新的头部和负载形成的签名,其与JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时使用的密钥的话,得出来的签名也是不一样的。

3. SpringCache注解有哪些参数?怎么配置的

使用到的是@Cacheable注解,参数有value,key,condition。
缓存的名字为value与key的组合,缓存名字可以使用方法中的参数或者#root.methodName
condition中的语句为真的话缓存,不为真就不缓存。

@Override
    @Transactional
    @Cacheable(value = {"BlogPage"},
            key = "#root.methodName" + "+'['+#queryPageBean.currentPage+']'",
            condition = "#queryPageBean.queryString==null")
    public Page<BlogVo> findHomePage(QueryPageBean queryPageBean) {
        //设置分页条件
        Page<BlogVo> page = new Page<>(queryPageBean.getCurrentPage(),
                queryPageBean.getPageSize());
        QueryWrapper<Blog> wrapper = new QueryWrapper<>();
        wrapper.like(queryPageBean.getQueryString() != null,
                "content", queryPageBean.getQueryString());
        page.setTotal(blogDao.selectCount(wrapper));
        page.setRecords(blogDao.findHomePage(queryPageBean));
        return page;
    }

在这里插入图片描述

4.redis存的数据是一直存放吗

过期会清理。

5. redis清理原理

Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
Redis实际使用的过期数据删除策略:

  • 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  • 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
Redis 内存淘汰机制:

  • volatile-lru(least recently used):从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  • allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  • allkeys-random:从数据集中任意选择数据淘汰
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

4.0 版本后增加以下两种:

  • volatile-lfu(least frequently used):从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
  • allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

6. Springboot自动装配原理

Spring Boot 通过 @EnableAutoConfiguration 开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional 按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。

SpringBoot启动的时候通过@EnableAutoConfiguration注解找到META-INF/spring.factories文件中的所有自动配置类,并对其加载,这些自动配置类都是以AutoConfiguration结尾来命名的。它实际上就是一个JavaConfig形式的IOC容器配置类,通过以Properties结尾命名的类中取得在全局配置文件中配置的属性,如server.port。

7. Spring Ioc Aop理解

IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

8. 除了动态代理,Spring还怎么实现aop

Spring AOP 基于动态代理,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
JDK动态代理只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。

9. 怎么判断链表是不是循环链表

采用快慢指针法,定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。

10. Spring怎么解决bean循环依赖

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
spring对循环依赖的处理有三种情况:

  • 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。
  • 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
  • 非单例循环依赖:无法处理。

Spring中循环依赖场景有:

(1)构造器的循环依赖

(2)field属性的循环依赖。

Spring的单例对象的初始化主要分为三步:

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖field循环依赖
在这里插入图片描述

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

举例:A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了

初始化的第一步(createBeanINstance实例化),并且将自己提前曝光到singletonFactories中。

此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过

ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。

此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

11. HashMap底层数据结构,key在哪,value在哪

JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。
JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

static final int hash(Object key) {
      int h;
      // key.hashCode():返回散列值也就是hashcode
      // ^ :按位异或
      // >>>:无符号右移,忽略符号位,空位都以0补齐
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      /* 
     h = key.hashCode() 为第一步:取hashCode值
     h ^ (h >>> 16)  为第二步:高位参与运算
    */
  }

在这里插入图片描述

HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(即数组下标,使用位运算代替取余操作,加快速度。这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
注意:(n - 1) & hash 是为了获取数组下标。

相关:HashMap 的长度为什么是 2 的幂次方
Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,内存是放不下的,用之前还要先做对数组的长度取模运算,这个数组下标的计算方法是“ (n - 1) & hash”。使用了位运算,所以数组长度用2 的幂次方。

12. HashMap怎么根据hash获取key,value

先说put。
简要流程如下:

  1. 首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;

  2. 如果数组是空的,则调用 resize 进行初始化;

  3. 如果没有哈希冲突直接放在对应的数组下标里;

  4. 如果冲突了,且 key 已经存在,就覆盖掉 value;

  5. 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;

  6. 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。

get方法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

计算key的hash值,找到对应hash值的节点,然后调用equals方法判断对应的key是否相等,如果相等则返回value。

13. ConcurrentHashMap怎么实现线程安全

JDK1.7中的ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即ConcurrentHashMap 把哈希桶切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。

Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。

static class Segment<K,V> extends ReentrantLock implements Serializable {
}

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

JDK1.8, ConcurrentHashMap 取消了 Segment 分段锁,采用 CASsynchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。

在这里插入图片描述

synchronized 只锁定当前链表红黑二叉树首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。

CAS:全称 Compare and swap,即比较并交换,它是一条 CPU 同步原语(原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断)。是一种硬件对并发的支持,针对多处理器操作而设计的一种特殊指令,用于管理对共享数据并发访问

CAS 是一种无锁非阻塞算法的实现。

CAS 包含了 3 个操作数:

需要读写的内存值 V
旧的预期值 A
要修改的更新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B更新 V 的值,否则不会执行任何操作(他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的)。

14. 谈谈Synchronized关键字,怎么实现线程安全

synchronized 关键字解决的是多个线程之间访问资源同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

作用或者三大特性

  • 原子性:确保线程互斥地访问同步代码,保证只有一个线程拿到锁;
  • 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  • 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”。

用法

  • 修饰普通方法:作用于当前对象实例,进入同步代码前要获得当前对象实例的锁
  • 修饰静态方法:作用于当前类,进入同步代码前要获得当前类对象的锁,synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁
  • 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁

但构造方法不能使用 synchronized 关键字修饰。
构造方法本身就属于线程安全的,不存在同步的构造方法一说。

使用例子,双重校验锁实现单例模式

public class Singleton {
   
    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
       //第一次校验singleton是否为空,
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                //第二次校验singleton是否为空
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

第二次校验是防止二次创建实例,线程A获得锁,生成实例,释放锁。线程B已经通过第一个if,获得锁,继续创建实例,出现创建多个实例的情况。
uniqueInstance 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

底层原理:

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor(每个对象中都内置了一个 ObjectMonitor对象) 的持有权。

  • 在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

  • 对象锁的的拥有者线程使用 monitorexit 指令来释放锁。在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。

synchronized 修饰的方法有ACC_SYNCHRONIZED 标识,指明该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
不过两者的本质都是对对象监视器 monitor 的获取。

15. jvm锁计数器在哪个区域

锁计数器存在于对象的ObjectMonitor中,也就是在对象实例中,在内存区域的里。

16. 有个concurrentHashMap,多个线程对其增删改查,是否线程安全,为什么

介绍:不安全。查的过程可能查到中间值,需要保证多个线程的同步性。

在更新数据时只会锁住部分数据,不会锁住整个表,读取的时候不能保证读取到最近的更新,只能保证读取到已经顺利插入的值。
面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?

17. 线程池的主要参数

  • corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。
  • maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize
  • keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。
  • workQueue :阻塞队列。ArrayBlockingQueue,LinkedBlockingQueue等,用来存放线程任务。
  • defaultHandler :饱和策略。ThreadPoolExecutor类中一共有4种饱和策略。通过实现RejectedExecutionHandler接口。
    – AbortPolicy : 线程任务丢弃报错。默认饱和策略。
    – DiscardPolicy : 线程任务直接丢弃不报错。
    – DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
    – CallerRunsPolicy :线程池之外的线程直接调用run方法执行。
  • ThreadFactory :线程工厂。新建线程工厂。

18. 知道哪些NoSQL数据库

redis

19. Mysql事务acid

  • 原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性(Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
  • 隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  • 持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

20. MySQL隔离级别,是怎么实现默认隔离级别的

read uncommited (读取未提交)
read commited (读取已提交)
repeatedable read (可重复读,默认隔离级别)
串行化
通过MVCC实现默认隔离级别

当前读(锁定读):
如果执行的是下列语句,就是 锁定读(Locking Reads)

select ... lock in share mode
select ... for update
insertupdatedelete 操作

在锁定读下,读取的是数据的最新版本,这种读也被称为 当前读(current read)。锁定读会对读取到的记录加锁:

  • select … lock in share mode:对记录加 S 锁,其它事务也可以加S锁,如果加 x 锁则会被阻塞

  • select … for update、insert、update、delete:对记录加 X 锁,且其它事务不能加任何锁

InnoDB 在实现Repeatable Read 时,如果执行的是当前读,则会对读取的记录使用 Next-key Lock ,来防止其它事务在间隙间插入数据

在 Repeatable Read 和 Read Committed 两个隔离级别下,如果是执行普通的 select 语句(不包括 select … lock in share mode ,select … for update)则会使用 一致性非锁定读(MVCC)。并且在 Repeatable Read 下 MVCC 实现了可重复读和防止部分幻读

快照读(非锁定读):
多版本控制 (multi versioning) 就是对非锁定读的实现。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)

隐藏字段,readview,undolog

隐藏字段:

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
  • DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

Read View 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”

主要有以下字段:

  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_id 为m_low_limit_id。小于这个 ID 的数据版本均可见
  • m_ids:Read View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID

在事务隔离级别 RC 和 RR (InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同

  • 在 RC 隔离级别下的 每次select 查询前都生成一个Read View (m_ids 列表)
  • 在 RR 隔离级别下只在事务开始后 第一次select 数据前生成一个Read View(m_ids 列表)

21. MySQL索引数据结构

索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。

MySQL索引默认数据结构为B+树

B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体

  • B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
  • B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
  • B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。

22. 什么是聚簇索引,什么是非聚簇索引

聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
非聚集索引即索引结构和数据分开存放的索引。

23. 给一个表,id,name,age,查询name=? && age = ? ,怎么建索引

name与age建立联合索引

24. select语句中联合索引的顺序有没有影响,如果写反了是怎么查询的

25. 一个10g的文件,里面都是字符串,要查找"hello"

全文索引

26. 怎么判断链表是不是循环链表

设置两个指针fast和slow,都指向头节点,一个一次移动两次,一个一次移动一次,如果,有一个时刻,他们两个相遇了,或者fast的next是slow(fast跑到了slow的前面),那么就代表有环。

京东

1.项目介绍,Springcloud提供服务的大概流程

2.elasticsearch 怎么保持跟数据库的同步(大量数据不能定时刷新), elasticsearch 查询为什么快

使用倒排索引。
采用的是 “关键词-文档” 矩阵,关键词与网页文档之间的映射关系是 一个关键词对应多个网页文档。

在这个索引中,Name、Color、Rate 这些字段被称为 filed, iphone 666 plus、blue、middle 这些被称作 Term,而 Term 对应的所有商品的 id 比如 [1, 3] 就是 Posting List。
当用户要查找 Color=blue 的商品时,通过索引三的 Term 和 Posting List 很快就可以找到,目标是 id 为 2 的商品,进而通过索引一找到商品 Name 为 华为 mate 98k。

3. logstash定时刷新有什么缺点

大数据量太慢,占用资源多。

4. 项目redis存的什么类型的数据,redis还有什么基本数据

String。String、Hash、Set、List、SortedSet。

  • 1、String:String是最常用的一种数据类型,普通的key- value 存储都可以归为此类。其中Value既可以是数字也可以是字符串。使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。

  • 2、Hash:Hash 是一个键值(key => value)对集合。Redishash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值。

  • 3、Set:Set是一个无序的天然去重的集合,即Key-Set。此外还提供了交集、并集等一系列直接操作集合的方法,对于求共同好友、共同关注什么的功能实现特别方便。

  • 4、List:List是一个有序可重复的集合,其遵循FIFO的原则,底层是依赖双向链表实现的,因此支持正向、反向双重查找。通过List,我们可以很方面的获得类似于最新回复这类的功能实现。

  • 5、SortedSet:类似于java中的TreeSet,是Set的可排序版。此外还支持优先级排序,维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

5. redis缓存命中率如何保证

6. redis缓存怎么保证插入新数据之后能在缓存中找到新数据(一致性)

@Caching(
            evict = {
                    @CacheEvict(value = "AdminBlog", key = "#uid"),
                    @CacheEvict(value = {"BlogPage"}, allEntries = true)
            })
    public boolean addBlog(AddBlogVo addBlogVo, Long uid) {}

先更新数据库,后删除缓存
在addBlog方法执行之后删除缓存

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。

allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。

beforeInvocation属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

7. 了解mq吗

8. 介绍一两个jvm垃圾回收器

CMS与G1
CMS(Concurrent Mark Sweep,并发标记清除) 收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。

从名字就可以知道,CMS是基于“标记-清除”算法实现的。CMS 回收过程分为以下四步:

初始标记 (CMS initial mark):主要是标记 GC Root 开始的下级(注:仅下一级)对象,这个过程会 STW,但是跟 GC Root 直接关联的下级对象不会很多,因此这个过程其实很快。

并发标记 (CMS concurrent mark):根据上一步的结果,继续向下标识所有关联的对象,直到这条链上的最尽头。这个过程是多线程的,虽然耗时理论上会比较长,但是其它工作线程并不会阻塞,没有 STW。

重新标记(CMS remark):顾名思义,就是要再标记一次。为啥还要再标记一次?因为第 2 步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾。

并发清除(CMS concurrent sweep):清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发进行的。

G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器。G1设计初衷就是替换 CMS,成为一种全功能收集器。G1 在JDK9 之后成为服务端模式下的默认垃圾回收器,取代了 Parallel Scavenge 加 Parallel Old 的默认组合,而 CMS 被声明为不推荐使用的垃圾回收器。G1从整体来看是基于 标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的。

G1 回收过程,G1 回收器的运作过程大致可分为四个步骤:

初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。

并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。

最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。

清理阶段(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。

9. innodb 与MyISAM区别

  • InnoDB 支持事务,MyISAM 不支持
  • InnoDB 支持外键,而 MyISAM 不支持
  • InnoDB 是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高;MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针,主键索引和辅助索引是独立的。
  • InnoDB 不保存表的具体行数,MyISAM 用一个变量保存了整个表的行数。
    MyISAM 采用表级锁(table-level locking);InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。

10. 项目难点

11. hashmap线程安全吗

不安全

12. Integer a =1000, Integer b =1000, a== b 输出什么

false
1000已经超出Integer缓冲池范围,是两个对象,==比较的是地址,所以不同

贝壳

场景题,多个线程怎么等都出了结果再结束

可以在主线程中使用join()

  • thread.join()
    主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
public static void test1() throws InterruptedException {
    List<Thread> threadSet = new ArrayList<>();
    for (int i = 1; i < 10; i++) {
        Thread thread = new Thread(() -> {
            //线程执行
            System.out.println("子线程执行");
        });
        thread.start();
        threadSet.add(thread);
    }
    for (Thread thread : threadSet) {
        thread.join();
    }
    System.out.println("子线程执行完,主线程继续执行");
}
  • CountDownLatch
    这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
public static void test2() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            //线程执行
            System.out.println("子线程执行");
            countDownLatch.countDown();
        });
        thread.start();
    }
    countDownLatch.await();
    System.out.println("子线程执行完,主线程继续执行");
}
  • CyclicBarrier
    CyclicBarrier 的源码实现和 CountDownLatch 大同小异,CountDownLatch 基于 AQS 的共享模式的使用,而 CyclicBarrier 基于 Condition 来实现的。在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。这就是实现一组线程相互等待的原理。
public static void test3() throws Exception {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            //线程执行
            System.out.println("子线程执行");
            try {
                cyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        thread.start();
    }
    cyclicBarrier.await();
    System.out.println("子线程执行完,主线程继续执行");
}

扩展:多线程顺序执行

两种常用的:join() 和Executors中的newSingleThreadExecutor()
调用join方法,会调用join(0)方法,当参数为0时,会调用wait方法,使主线程(main)阻塞,
/等待子线程执行完毕后,主线程结束等待,继续执行。


//调用join方法,会调用join(0)方法,当参数为0时,会调用wait方法,使主线程(main)阻塞,
        // 等待子线程执行完毕后,主线程结束等待,继续执行。
static ExecutorService executorService = Executors.newSingleThreadExecutor();

    static Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("thread1 运行");
        }
    });

    static Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("thread2 运行");
        }
    });

    static Thread thread3 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("thread3 运行");
        }
    });
        
        System.out.println("main开始运行");
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
        thread3.join();
        System.out.println("main运行结束");

使用Executors中的newSingleThreadExecutor()
创建一个单线程的线程池,也可以达到控制线程执行顺序的目的。
/但是不能保证子线程和主线程的执行顺序
newSingleThreadExecutor()方法创建的线程池是一个基于FIFO(先进先出)的队列
也就是说,当我们依次将thread1,thread2,thread3加入队列中时
实际在就绪状态的只有thread1这个线程,thread2,thread3则会被添加到队列中等待
当thread1执行完毕后,则会按进入队列的先后顺序执行队列中的其他线程。

//使用Executors中的newSingleThreadExecutor()
        // 创建一个单线程的线程池,也可以达到控制线程执行顺序的目的。
        // 但是不能保证子线程和主线程的执行顺序
        //newSingleThreadExecutor()方法创建的线程池是一个基于FIFO(先进先出)的队列
        // 也就是说,当我们依次将thread1,thread2,thread3加入队列中时
        // 实际在就绪状态的只有thread1这个线程,thread2,thread3则会被添加到队列中等待
        // 当thread1执行完毕后,则会按进入队列的先后顺序执行队列中的其他线程。

static ExecutorService executorService = Executors.newSingleThreadExecutor();

        System.out.println("main开始运行");
        executorService.submit(thread1);
        executorService.submit(thread2);
        executorService.submit(thread3);
        System.out.println("main运行结束");

Redis数据持久化方式

快照,AOF

项目中Token直接解析可能会出安全问题,应当存到mysql中,注意token过期时间

Redis数据结构

前后端怎么部署

前端用nginx,打包为docker镜像,后端jar包

volatail关键字

悲观锁,乐观锁

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现:

  • CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  • 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

Hashmap介绍

项目中Redis怎么保持缓存一致性

先更新数据库,后删除缓存

jvm内存模型,例如volatile

Java 还提供了种弱形式的同步,也就是使用 volatile 关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。
当一个变量被声明为volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。 当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。

原理:内存屏障
内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:

  • 阻止屏障两侧的指令重排序;
  • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

  • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
  • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

Mysql写一个sum语句,在sum的字段上加索引与不加索引查询速度是不是不一样

应该是一样的。

索引失效情况

1、使用!= 或者 < > 导致索引失效
2、类型不一致导致的索引失效
3、函数导致的索引失效
如:

SELECT * FROM `user` WHERE DATE(create_time) = '2020-09-03';

如果使用函数在索引列,这是不走索引的。

4、运算符导致的索引失效

SELECT * FROM `user` WHERE age - 1 = 20;

如果你对列进行了(+,-,*,/,!), 那么都将不会走索引。

5、OR引起的索引失效

SELECT * FROM `user` WHERE `name` = '张三' OR height = '175';

OR导致索引是在特定情况下的,并不是所有的OR都是使索引失效,如果OR连接的是同一个字段,那么索引不会失效,反之索引失效。

6、模糊搜索导致的索引失效

SELECT * FROM `user` WHERE `name` LIKE '%冰';

当%放在匹配字段前是不走索引的,放在后面才会走索引。

7、NOT IN、NOT EXISTS导致索引失效

网易杭州研究院

1. 介绍项目

2. 学习java中或者项目中印象比较深的地方。答springboot启动注解,简化配置(埋坑)

3. 自己答Springboot 启动类注解,starter等

4. springboot 项目启动时的流程,生命周期

在这里插入图片描述

5. spring bean生命周期

在这里插入图片描述

1,实例化bean对象,以及设置bean属性;
2,如果通过Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖,Aware接口是为了感知到自身的一些属性。容器管理的Bean一般不需要知道容器的状态和直接使用容器。但是在某些情况下是需要在Bean中对IOC容器进行操作的。这时候需要在bean中设置对容器的感知。SpringIOC容器也提供了该功能,它是通过特定的Aware接口来完成的。 比如BeanNameAware接口,可以知道自己在容器中的名字。 如果这个Bean已经实现了BeanFactoryAware接口,可以用这个方式来获取其它Bean。 (如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 如果Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。)
3,紧接着会调用BeanPostProcess的前置初始化方法postProcessBeforeInitialization,主要作用是在Spring完成实例化之后,初始化之前,对Spring容器实例化的Bean添加自定义的处理逻辑。有点类似于AOP。
4,如果实现了BeanFactoryPostProcessor接口的afterPropertiesSet方法,做一些属性被设定后的自定义的事情。
5,调用Bean自身定义的init方法,去做一些初始化相关的工作。
6,调用BeanPostProcess的后置初始化方法,postProcessAfterInitialization去做一些bean初始化之后的自定义工作。
7,完成以上创建之后就可以在应用里使用这个Bean了。

6. 项目哪里用到了刚才说的知识。

自定义注解

7. 还熟悉哪些java知识 。

jvm内存结构,垃圾回收。详细介绍

8. 内存结构分为堆与非堆,讲一讲作用

堆,虚拟机栈,本地方法栈,程序计数器,方法区(元空间)

9. G1垃圾回收器什么时候暂停所有线程

初始标记
最终标记
筛选回收

10. 数据结构,图了解吗,树?说下堆排序过程(忘)

11. 你了解什么排序(快排)详细说明快排过程。

从数列中挑出一个元素,称为 “基准”(pivot);

重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

12. 快排时间复杂度,最坏时间复杂度,有什么优缺点

O(NlogN),O(N2)

优点:平均性能好,O(nlog2n),2为下标

缺点:不稳定,初始序列有序或基本有序时,时间复杂度降为O(n^2)。

恒生电子

1. 项目

2. spring cloud大概哪几部分

服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config

3. maven 做什么的

在项目的 parent 层,可以通过 dependencyManagement 元素来管理 jar 包的版本,让子项目中引用一个依赖而不用显示的列出版本号

统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,才能保证测试的和发布的是相同的成果,因此,在顶层 pom 中定义共同的依赖关系。同时可以避免在每个使用的子项目中都声明一个版本号,这样想升级或者切换到另一个版本时,只需要在父类容器里更新,不需要任何一个子项目的修改;如果某个子项目需要另外一个版本号时,只需要在 dependencies 中声明一个版本号即可。子类就会使用子类声明的版本号,不继承于父类版本号

4. jwt自己写的吗

5. 四百万数据找出重复Url并计数怎么做

在这里插入图片描述

6. gateway了解吗

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,
Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。
比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。
比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

在这里插入图片描述

7. 智力题,一百只箭,两个人,每个人可先射可后射,射一支或两支,若保证A必须赢,怎么定规则。

A先射第一支,然后如果对方射两支,A就射一支,对方射一支,A射两支

8. 了解消息队列吗

9. cpu占用率太高怎么排查

CPU飙高 | 死循环
我们有个线上应用,单节点在运行一段时间后,CPU 的使用会飙升,一旦飙升,一般怀疑某个业务逻辑的计算量太大,或者是触发了死循环(比如著名的 HashMap 高并发引起的死循环),但排查到最后其实是 GC 的问题。

(1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。

top

(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。

top -Hp $pid

(3)使用 printf 函数,将十进制的 tid 转化成十六进制。

printf "%x\n" tid
386e

(4)然后使用jstack命令定位到,程序的哪一行出了问题

jstack pid |grep tid -A60

10. 有一个coin表,一个字段表示正反,怎么写sql将正的变反,反的变正

update coin
set flag =
        case
            when zheng then fan
            ELSE
                zheng
            end
;

百度

1. 算法:连续子数组的最大和

2. final关键字

final、finally、finalize的区别
final 用于修饰变量方法

final 变量:被修饰的变量不可变,不可变分为引用不可变对象不可变,final 指的是引用不可变,final 修饰的变量必须初始化,通常称被修饰的变量为常量。
final 方法:被修饰的方法不允许任何子类重写,子类可以使用该方法。
final :被修饰的类不能继承,所有方法不能被重写
finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。

finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。

一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。

3. 对象的引用类型

  • 强引用:
    以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
  • 软引用:
    用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。SoftReference 实现
  • 弱引用:
    如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
  • 虚引用:
    顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
    虚引用主要用来跟踪对象被垃圾回收的活动。
    虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

4. 继承与抽象

继承:
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

  • 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
  • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。

5. 父类子类初始化执行顺序

基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块

代码块执行顺序静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块

继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器

6. 抽象类与接口,接口不能有默认实现吗?

共同点 :

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法)。

区别 :

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系(比如说我们抽象了一个发送短信的抽象类,)。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

7. hashcode()与equals(),有两者不同的情况

如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

hashMap就有hashCode相同,equals()不一定相同的情况。哈希冲突

8. 锁升级过程,锁能降级吗

上面讲到锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆

偏向锁
一句话总结它的作用:减少统一线程获取锁的代价。在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得,那么此时就是偏向锁。

核心思想:

如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word锁标记位为偏向锁以及当前线程ID等于Mark WordThreadID即可,这样就省去了大量有关锁申请的操作。

轻量级锁
轻量级锁是由偏向锁升级而来,当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁。注意这里的第二个线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。

重量级锁
重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。

重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。

注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

9. volatile关键字

10. 线程池添加任务过程,核心线程一定会创建新线程吗

   // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
   private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static int workerCountOf(int c) {
        return c & CAPACITY;
    }
    //任务队列
    private final BlockingQueue<Runnable> workQueue;

    public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。
        if (command == null)
            throw new NullPointerException();
        // ctl 中保存的线程池当前的一些状态信息
        int c = ctl.get();

        //  下面会涉及到 3 步 操作
        // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
        // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
        // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
            if (!isRunning(recheck) && remove(command))
                reject(command);
                // 如果当前线程池为空就新创建一个线程并执行。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
        else if (!addWorker(command, false))
            reject(command);
    }

11. jvm内存结构

jvm将虚拟机分为5大区域,程序计数器、虚拟机栈、本地方法栈、java堆、方法区;

程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址;
虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;
本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;
堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在方法区了,被元数据区替代了,原方法区被分成两部分;1:加载的类信息,2:运行时常量池;加载的类信息被保存在元数据区中,运行时常量池保存在堆中;

12. OOM怎么排查问题

之前网易面试被问什么是OOM?为什么会出现OOM?怎么解决?

13. Error, Runnable Exception与非 Runnable Exception

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。例如Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException…。

Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为非受检查异常,例如:NullPointerException、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等。

14. MySql隔离级别,默认隔离级别,怎么避免的脏读

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读,默认隔离级别): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

MVCC
ReadView,undolog,隐藏字段

在内部,InnoDB 存储引擎为每行数据添加了三个 隐藏字段:

DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

15. sql语句执行过程

  • MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
  • 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
  • 查询语句的执行流程如下:权限校验(如果命中缓存)—>查询缓存—>分析器—>优化器—>权限校验—>执行器—>引擎
  • 更新语句执行流程如下:分析器---->权限校验---->执行器—>引擎—redo log(prepare 状态)—>binlog—>redo log(commit状态)

16. mysql编程

user: userid productid productname
product: productid date pay
找到userid=100的7天内的各个商品的总额

招联金融

一面

java对象产生的过程

Step1:类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

Step2:分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
内存分配的两种方式:(补充内容,需要掌握)

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
在这里插入图片描述
内存分配并发问题(补充内容,需要掌握)

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

Step3:初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

#Step5:执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

java对象包括哪些内容

在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:

  • 对象头
  • 实例数据
  • 对齐填充

Hotspot 虚拟机的对象头包括两部分信息

  • 用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等)
  • 类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容

对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

synchronized锁原理,特性

synchronized是轻量级锁吗

JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

锁升级?

在锁对象的对象头里面有一个 threadid 字段,
在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,
再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,
执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

volatile关键字作用,原理

Java 还提供了种弱形式的同步,也就是使用 volatile 关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。
当一个变量被声明为volatile 时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。 当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。

原理:内存屏障
内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:

  • 阻止屏障两侧的指令重排序;
  • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

  • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
  • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;
public class MemoryBarrier {
    int a, b;
    volatile int v, u;

    void f() {
        int i, j;

        i = a;
        j = b;
        i = v;
        //LoadLoad
        j = u;
        //LoadStore
        a = i;
        b = j;
        //StoreStore
        v = i;
        //StoreStore
        u = j;
        //StoreLoad
        i = u;
        //LoadLoad
        //LoadStore
        j = b;
        a = i;
    }
}

怎么产生数组,讲讲各个集合

hashmap 1.7版本有个循环链表问题,了解吗

A线程在插入节点B,B线程也在插入,遇到容量不够开始扩容,重新hash,放置元素,采用头插法,后遍历到的B节点放入了头部,这样形成了环,如下图所示:
在这里插入图片描述

还熟悉哪块(网络),socket编程吗?

mySql怎么防止脏读

怎么防止幻读(告诉我单纯当前读和快照读都不会产生幻读)

可重复读怎么实现防止幻读

二面

反射,(没答上来)

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

优点 : 可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

反射的方法有哪些 (忘了)

  • Class.forName(“类的路径”);当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
  • 类名.class。这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
  • 对象名.getClass()。
String str = new String("Hello");
Class clz = str.getClass();
  • 如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。
Class c4= Integer.TYPE;

Spring bean怎么产生

sleep跟wait区别

  • 两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
  • 两者都可以暂停线程的执行。
  • wait() 通常被用于线程间交互/通信,sleep() 通常被用于暂停执行。
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒。

mysql隔离级别

RU, RC, RR,SERIALIZABLE

mysql实现串行化

串行化就相当于给操作的记录上一个共享锁(读写锁),即当读某条记录时就占用这条记录的读锁,此时其它事务一样可以申请到这条记录的读锁来读取,但是不能写(读锁被占的话,写锁就不能被占;读锁可以被多个事务同时占有)

讲讲幻读

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读的重点是修改,幻读的重点在于新增或者删除。

mysql并发操作事务怎么防止错误

乐观锁悲观锁

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

  • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。

redis数据结构

数据结构与算法了解吗,讲一下快排

树的深度优先遍历

深度优先遍历指的是,从树的根节点开始,先遍历左子树,然后遍历右子树。

趋势科技

双向循环链表头插尾插实现

sql题,学号,成绩,课程号,找出各科排名前5的信息

dockfile格式

Dockerfile指令说明简洁版:

FROM
构建镜像基于哪个镜像

MAINTAINER
镜像维护者姓名或邮箱地址

RUN
构建镜像时运行的指令

CMD
运行容器时执行的shell环境

VOLUME
指定容器挂载点到宿主机自动生成的目录或其他容器

USER
为RUN、CMD、和 ENTRYPOINT 执行命令指定运行用户

WORKDIR
为 RUN、CMD、ENTRYPOINT、COPY 和 ADD 设置工作目录,就是切换目录

HEALTHCHECH
健康检查

ARG
构建时指定的一些参数

EXPOSE
声明容器的服务端口(仅仅是声明)

ENV
设置容器环境变量

ADD
拷贝文件或目录到容器中,如果是URL或压缩包便会自动下载或自动解压

COPY
拷贝文件或目录到容器中,跟ADD类似,但不具备自动下载或解压的功能

ENTRYPOINT
运行容器时执行的shell命令

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
FROM:
定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。
RUN:
用于执行后面跟着的命令行命令。有以下俩种格式:

RUN <命令行命令>
# <命令行命令> 等同于,在终端操作的 shell 命令。
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

COPY
复制指令,从上下文目录中复制文件或者目录到容器里指定路径。

COPY hom* /mydir/
COPY hom?.txt /mydir/

ADD
ADD 指令和 COPY 的使用格类似(同样需求下,官方推荐使用 COPY)。功能也类似,不同之处如下:

  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。

CMD
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:

  • CMD 在docker run 时运行。
  • RUN 是在 docker build。

作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

CMD <shell 命令> 
CMD ["<可执行文件或命令>","<param1>","<param2>",...] 
CMD ["<param1>","<param2>",...]  # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数

ENTRYPOINT
类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。

  • 优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

  • 注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

ENTRYPOINT ["<executeable>","<param1>","<param2>",...]

可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。

示例:

假设已通过 Dockerfile 构建了 nginx:test 镜像:

FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参 

ENV
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

ARG
构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

格式:

ARG <参数名>[=<默认值>]

VOLUME
定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

作用:

  • 避免重要的数据,因容器重启而丢失,这是非常致命的。
  • 避免容器不断变大。
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

EXPOSE
仅仅只是声明端口。

作用:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
EXPOSE <端口1> [<端口2>...]

Linux命令,怎么查进程状态,怎么找到占用率高的java进程

CPU飙高 | 死循环
我们有个线上应用,单节点在运行一段时间后,CPU 的使用会飙升,一旦飙升,一般怀疑某个业务逻辑的计算量太大,或者是触发了死循环(比如著名的 HashMap 高并发引起的死循环),但排查到最后其实是 GC 的问题。

(1)使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。使用 Shift + P 快捷键可以按 CPU 的使用率进行排序。

top

(2)再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。

top -Hp $pid

(3)使用 printf 函数,将十进制的 tid 转化成十六进制。

printf "%x\n" 14446
386e

(4)然后使用jstack命令定位到,程序的哪一行出了问题

jstack 14445 |grep 386e -A60

介绍下JWT

JWT 的三个部分依次如下。

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的 JSON 对象使用 Base64URL 算法)转成字符串。

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

JWT签名有什么用

header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据。
signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值。

见过线程阻塞的情景吗,一个线程阻塞了还能唤醒吗

如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。以下是详细的唤醒方法:

  1. sleep() 方法

sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。(暂停线程,不会释放锁)

2.suspend() 和 resume() 方法:

挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(不建议用,容易发生死锁)

  1. yield() 方法:

会使得线程放弃当前分得的cpu时间片,但此时线程仍然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。调用yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)

4.wait() 和 notify() 方法

两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁住的对象,然后再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)

5.join()方法

也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。

以上是Java线程唤醒和阻塞的五种常用方法,不同的方法有不同的特点,其中wait() 和
notify()是其中功能最强大、使用最灵活的方法,但这也导致了它们效率较低、较容易出错的特性,因此,在实际应用中应灵活运用各种方法,以达到期望的目的与效果!

因为抢占共享资源引起的阻塞能不能用notify唤醒

项目中vue页面跳转用的什么,原理是什么

用友

数据库主键什么形式

不是自增主键,如果换数据库可能会用不到

分库分表,使用了雪花算法生成主键

代码有去重为什么用分布式锁

类的生命周期

Spring源码

HashMap1.7存在的问题

度小满一面(9/03 40min)

项目分布式锁,为什么要用分布式锁,如果不用分布式锁,怎么解决重复发送问题(前端?后端怎么解决)?

数据库邮箱字段加唯一索引

如果redis分布式锁到期,但任务没完成,该怎么办

https://www.zhangshilong.cn/work/88878.html

  1. Redission看门狗自动延期机制
  2. 自定义锁续约计划
  • 在锁定期间打开另一个线程调用持久锁定的代码。 下面介绍一下接下来的代码。 主要步骤如下:
    基于密钥key获取具有密钥的线程的key中的字段和值。 根据自己设置的锁定超时时间,定义自旋时间。
    开始旋转,重新获取当前redis的锁定密钥字段和值。 查看再次获取的字段和值是否存在,如果不存在,则结束继续锁定操作。
    如果存在,则进入下一步。 判断当前锁定密钥的字段名和需要继续锁定密钥的字段名是否相同,否则可以结束继续锁定操作。 同样地进行下一步。
    获取锁定的剩馀时间,判断剩馀时间是否小于锁定超时时间的1/4。 是的,重新设置超时时间,使具有锁定的线程继续具有锁定。
    是否需要锁定,每超时时间的1/4进行锁定继续的判断

Mysql主键是什么?雪花算法生成id的缺点?

雪花算法生成主键。
缺点:在获取时间的时候,可能会出现时间回拨的问题,就是服务器上的时间突然倒退到之前的时间
解决:

  • 当时间回拨较小时,等待时钟同步到最后一次主键生成的时间后再继续工作。

    当时间回拨较大时,可以对序列化的初始值设置步长,每次触发时钟回拨事件,则其初始步长就加1w,可以在下面代码的第85行来实现,将sequence的初始值设置为10000。

提到了唯一索引,Mysql唯一索引能否解决重复问题

可以解决。

Mysql慢查询影响有哪些

  • 影响用户体验。慢sql的执行时间过长,则会导致用户的等待时间过长,直接影响用户体验。
  • 造成数据库幻读、不可重复读。假设该慢sql是一个更新操作的sql,则会可能出现幻读、不可重复读这种数据库并发事务导致的问题。
  • InnoDB的慢查会造成DDL操作阻塞。 慢查可能导致占用mysql的大量内存导致mysql服务直接挂掉导致整个系统瘫痪。
  • 慢查sql可能执行时间过长导致应用的进程被kill无法返回结果给到客户端。

查Mysql的话,极端情况下Mysql主从可能会导致查询不到已经插入的数据,导致数据重复插入

  • 配合 semi-sync 半同步复制;
  • 一主多从,分摊从库压力;
  • 强制走主库方案(强一致性);
  • sleep 方案:主库更新后,读从库之前先sleep 一下; 判断主备无延迟方案(例如判断 seconds_behind_master 参数是否已经等于 0、对比位点);
  • 并行复制— 解决从库复制延迟的问题

Mysql索引为什么快,聚簇索引与非聚簇索引区别

  • 聚簇索引叶子节点存储的是行数据;而非聚簇索引叶子节点存储的是聚簇索引(通常是主键 ID)。
    聚簇索引查询效率更高,而非聚簇索引需要进行回表查询,因此性能不如聚簇索引。
    聚簇索引一般为主键索引,而主键一个表中只能有一个,因此聚簇索引一个表中也只能有一个,而非聚簇索引则没有数量上的限制。

HashMap特性,为什么设置负载因子,什么时候扩容,链表与树的优缺点,HashMap红黑树能转换为链表吗?

如果我们把负载因子设置成1,容量使用默认初始值16,那么表示一个HashMap需要在"满了"之后才会进行扩容。那么在HashMap中,最好的情况是这16个元素通过hash算法之后分别落到了16个不同的桶中,否则就必然发生哈希碰撞。而且随着元素越多,哈希碰撞的概率越大,查找速度也会越低。

如果负载因子设置为0.5,那么就会频繁的扩容,浪费空间

当红黑树中的元素减少并小于一定数量时,会切换回链表

Mysql事务特性,什么时候会用到事务,如何保证原子性

undo log。undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子

Spring事务失效场景,为什么失效

一、访问权限:

spring事务的实现AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务

二、方法用final修饰

spring事务底层实现使用了代理,aop,通过jdk的动态代理或者cglib,生成了代理类,在代理类中实现了事务功能,如果方法被final修饰,无法重写该方法,也就无法添加事务的功能了

三、方法内部调用

在同一个类的service中,调用其他的事务方法,由于spring的事务实现是因为aop生成代理,这样是直接调用了this对象,所以也不会生成事务。
解决:

  • 增加一个service,把一个事务的方法移到新增加的service方法里面,然后进行注入再调用
  • 在自己类中注入自己

四、没有被spring管理

在使用spring事务的时候,对象要被spring进行管理,也就是需要创建bean,一般我们都会加@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。,如果忘记加了,也会导致,事务的失效

五、多线程调用

但是事务方法在另一个线程里面调用,这样会导致两个方法在不同的一个线程中,获取的数据库连接也不一样,所以会是两个不同的事务。
看过spring源码,我们可以知道,spring的事务是通过连接数据库来实现的,当前线程保存了一个map,key—数据源,value----数据库连接,事务其实就是指向同一个连接的,只有拥有同一个数据库连接才能同时提交和回滚,如果在不同的线程,数据库的连接不是同一个,所以事务也不是同一个。

六、设计的表不支持事务

MyISAM,可能一些老的项目还在使用,但是他是不支持事务的

七、没有开启事务

如果创建的不是springboot项目可能会导致这样的问题出现,因为springboot项目有自动装配的类DataSourceTransactionManagerAutoConfiguration,已经默认开启了事务,配置spring.datasource参数就行,如果是spring项目,需要在applicationContext.xml配置的,不然事务不会生效

八、错误的事务传播

使用了没有事务的事务传播特性

九、自己捕获了异常

可能是我们在写代码的时候自己在代码手动进行了try…catch

@Transactional
    public  void query(Demo demo) {
        try {
            save(demo);
        } catch (Exception e) {
            System.out.println("异常");
        }
    }

这种情况下,spring事务不会进行回滚,因为我们进行了手动捕获异常,然后没有手动抛出,如果想要spring事务的正常回滚,必须抛出它能处理的异常,如果没有抛出异常,spring会认为程序没有问题。

十、手动抛出别的异常

十一、自定义回滚异常

十二、嵌套事务回滚过头

十三、编程式事务

未用@Transactional注释的方法调用被@Transactional注释的方法,怎么解决Spring事务失效

放入不同的类中

项目用到的Spring特性 (IOC,AOP)

Spring IOC容器初始化过程

Spring后置处理了解吗,主要应用

字节番茄小说一面(36min)

HTTP与HTTPS区别

  • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
  • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
  • HTTP 的端口号是80,HTTPS 的端口号是 443。
  • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

输入www.baidu.com的过程

在浏览器中输入 url 地址 ->> 显示主页的过程

每次HTTP都经过TCP吗

HTTP/1.0 默认使用短连接 ,也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个TCP连接,这样就会导致有大量的“握手报文”和“挥手报文”占用了带宽。

为了解决 HTTP/1.0 存在的资源浪费的问题, HTTP/1.1 优化为默认长连接模式 。 采用长连接模式的请求报文会通知服务端:“我向你请求连接,并且连接成功建立后,请不要关闭”。因此,该TCP连接将持续打开,为后续的客户端-服务端的数据交互服务。也就是说在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。

HashMap数据结构

JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

HashMap什么时候扩容,扩容时间复杂度

扩容过程:

  • 若threshold(阈值)不为空,table的首次初始化大小为阈值,否则初始化为缺省值大小16
  • 默认的负载因子大小为0.75,当一个map填满了75%的bucket时候,就会扩容,扩容后的table大小变为原来的两倍
  • 假设扩容前的table大小为2的N次方,有上述put方法解析可知,元素的table索引为其hash值的后N位确定
  • 扩容后的table大小即为2的N+1次方,则其中元素的table索引为其hash值的后N+1位确定,比原来多了一位
  • 重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing

因此,table中的元素只有两种情况:
元素hash值第N+1位为0:不需要进行位置调整
元素hash值第N+1位为1:调整至原索引的两倍位置
扩容或初始化完成后,resize方法返回新的table

Redis Zset数据结构

相比于set,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。
zset有两种不同的实现,分别是zipList和skipList。

zipList:
满足以下两个条件:

  • [score,value]键值对数量少于128个;
  • 每个元素的长度小于64字节;

skipList:
不满足以上两个条件时使用跳表(组合了hash和skipList)

  • hash用来存储value到score的映射,这样就可以在O(1)时间内找到value对应的分数;
  • skipList按照从小到大的顺序存储分数;
  • skipList每个元素的值都是[score,value]对

Redis 中 zset 不是单一结构完成,是跳表和哈希表共同完成
实现方式:Redis sorted set的内部使用HashMap和跳跃表(skipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

Zset依据什么排序

score

进程与线程区别

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程

进程间通信

  • 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
  • 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in
    first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
  • 信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
  • 消息队列(Message Queuing):消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比
    FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
  • 信号量(Semaphores):信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
  • 共享内存(Shared memory):使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
  • 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP
    的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

进程切换时都有哪些改变

  1. 切换内核态堆栈(由于进程切换需要陷入内核态,用户态到内核态的切换,必然会涉及到内核态堆栈的切换)

  2. PCB的切换(重新引起调度时,操作系统会找到新的进程的PCB,并完成该进程与新进程PCB的切换,寄存器的值,硬件上下文)

    —ip(instruction pointer):指向当前执行指令的下一条指令

    —bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址

    —sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址

    —cr3:页目录基址寄存器,保存页目录表的物理地址

  3. 切换页表全局目录(由于进程是独享内存空间的,进程对内存的操作涉及到虚拟内存,页表是实现方式,所以切进程内存,就是切换页表)

4、刷新TLB(TLB本质是一种cache,用于完成虚拟地址和物理地址间的转换)

进程切换时操作系统做了什么

上下文切换包括保存当前任务的运行环境,恢复将要运行任务的运行环境。当进程被切换时,操作系统内核必须先保护现场,即将处理机状态信息保存在相应的PCB中,以便在该进程重新执行时能再从断点继续执行。然后恢复另一个进程的状态,当前运行任务转为就绪(或者挂起)状态,另一个被选定的就绪任务成为当前任务

算法题

合并区间
岛屿数量

顺丰一面

介绍下TCP

TCP在握手阶段和非握手阶段丢包的话怎么解决

握手阶段

TCP第一次握手SYN丢包

当客户端发起的TCP第⼀次握手SYN 包,在超时时间内没收到服务端的ACK,就会在超时重传SYN数据包,每次超时重传的RTO是翻倍上涨的,直到 SYN 包的重传次数到达 tcp_syn_retries值后,客户端不再发送 SYN 包。

TCP第二次握手SYN + ACK丢包

当TCP第⼆次握⼿ SYN、ACK 包丢了后,客户端 SYN 包会发生超时重传,服务端SYN、ACK 也会发生超时重传。 客户端SYN包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是5次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是5次。

TCP第三次握手ACK丢包

在建立TCP连接时,如果第三次握手的ACK,服务端无法收到,则服务端就会短暂处于SYN_RECV 状态,而客户端会处于ESTABLISHED状态。 由于服务端⼀直收不到TCP第三次握⼿的ACK,则会⼀直重传SYN、ACK包,直到重传次数超过tcp_synack_retries 值(默认值5次)后,服务端就会断开 TCP连接。
而客户端则会有两种情况

  1. 如果客户端没发送数据包,⼀直处于 ESTABLISHED
    状态,然后经过2个小时11分15秒(TCP保活机制)才可以发现⼀个死亡连接,于是客户端连接就会断开连接。
  2. 如果客户端发送了数据包,⼀直没有收到服务端对该数据包的确认报文,则会⼀直重传该数据包,直到重传次数超过
    tcp_retries2值(默认值 15 次)后,客户端就会断开TCP连接。

非握手阶段

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。
  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  3. 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. TCP 的接收端会丢弃重复的数据。
  5. 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
  6. 拥塞控制: 当网络拥塞时,减少数据的发送。
  7. ARQ 协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
  8. 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段

TCP怎么实现拥塞控制

TCP 的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。

  • 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
  • 拥塞避免: 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送放的 cwnd 加 1.
  • 快重传与快恢复: 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。

二分查找(写的有些瑕疵,用的是System.out.print不是return,结果一直输出)

怎么实现优先队列

红黑树与堆做优先队列,好处与坏处

瞎扯

红黑树与堆的数据结构的优缺点(时间复杂度,查询等方面)

堆的话只要弹出顶部元素即可,红黑树需要查询

linux了解吗

复习linux命令

Redis为什么快

Redis为什么快

Redis数据类型

  1. string:字符串,可存储数字,xml,json,png文件等。
  2. list:列表,可在左边或右边增加元素,元素有序可重复。
  3. hash:哈希,存储kay,value键值对
  4. set:无序唯一
  5. zset:有序唯一,可用于排行榜

sortedSet可以做优先队列吗,数据结构是什么样的

可以 基于 Redis 的优先级队列

  • 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
  • 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
  • 应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

压缩表或跳表

ziplist,即压缩列表
zipList:
满足以下两个条件:

  • [score,value]键值对数量少于128个;
  • 每个元素的长度小于64字节;

压缩列表是由连续性内存组成的顺序性数据结构,一个压缩列表可以包含任意多的entry,每个entry可以保存一个字节数组或者一个整数。
压缩列表在表头有三个字段:zlbytes,zltail,zllen分别表示列表长度(整个列表占用的字节数),列表尾的偏移量(尾节点距离起始地址的字节数)和列表中entry的个数。
列表表尾还有一个zlend,表示列表结束了。

跳表的查找会从顶层链表的头部元素开始,然后遍历该链表,直到找到元素大于或等于目标元素的节点,如果当前元素正好等于目标,那么就直接返回它。如果当前元素小于目标元素,那么就垂直下降到下一层继续搜索,如果当前元素大于目标或到达链表尾部,则移动到前一个节点的位置,然后垂直下降到下一层。
Redis数据结构之Zset

Mysql遇到慢查询怎么排查,explain中有个type字段,是做什么的

  • 开启慢查询日志slow_query_log=1,并设置慢查询时长标准long_query_time
  • 查看慢查询日志,使用mysqldumpslow命令,可根据访问次数、返回记录数、查询时间、锁定时间、后三个的平均时间,如平均返回记录数,可根据这些进行排序,指定查看top几,还可以正则匹配。
  • 定位到要优化的sql后,explain 慢sql查看执行计划。

explain的结果
id相同的为一组,组内的按出现顺序执行,id越大的组优先执行。
table:查询的表
possible_keys:可能的索引
key:实际使用的索引
rows:mysql估计要扫描的行数
type:显示使用了何种查询,效率从高到低为system>const>eq_ref>ref>range>index>all

system,const mysql能将查询优化成常量,例如
eq_ref:使用了唯一索引。主键索引、唯一索引
ref:非唯一索引
range:使用了between and><in
index:扫描所有索引行
all:扫描所有数据行,全表扫描

extra:一些额外关键信息。

using index:使用了覆盖索引
using filesort:索引创建数据排序顺序不符合要求,在外部重新排序
using temporary:使用了临时表来保存信息
using where:使用了where
using join buf:关联表未使用索引,且需要缓存来保存中间结果

mysql 慢查询原因 排查 优化

进程与线程区别

进程与线程状态

sleep会进入什么状态

sleep的线程是处于TIMED_WAITING状态

联影一面

项目流程介绍,难点

redis分布式锁原理,如果业务处理较慢,分布式锁过期怎么办

设置守护线程,定期检查resdis锁是否存在,如果存在就续约,不存在表示任务已完成

有经历过sql慢查询吗

参考顺丰一面

mysql联合索引a,b,c如果只用ac走不走索引,musql8.0的话只用bc走不走索引

只用ac,只有a走了索引。
不包含最左侧的a不走索引
mysql 联合索引 复合索引(abc)如何索引命中规则实测

static关键字,static修饰的类能否调用实例对象

Java基本数据类型

String是基本类型吗

包装类型,怎么自动拆箱装箱,隐式装箱拆箱原理

自动装箱过程是通过调用包装类valueOf()方法实现的,而自动拆箱过程是通过调用包装类 xxxValue()方法实现的(xxx代表对应的基本数据类型,如intValue()、doubleValue()等)

隐式装箱拆箱原理是编译器实现

Java权限修饰符

权限修饰符的总结:
权限修饰符的大小依次是public protected default private
它们的大小指的是可以被调用的范围。
在这里插入图片描述

hashCode与equals,如果hashcode一样,两个对象一定一样吗

Java参数传递方式

值传递

Java类加载过程

类加载过程详解

HashMap线程安全吗,什么时候扩容,怎么解决hash冲突

Java线程安全的Map,底层原理,数据结构

ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))

synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。

HashMap的key可以是对象吗?

在HashMap存储自定义对象的时候,需要自己再自定义的对象中重写其hashCode()方法和equals方法,才能保证其存储不重复的元素,否则将存储多个重复的对象,因为每new一次,其就创建一个对象,内存地址是不同的

项目有用到多线程吗?为什么使用线程池而不是新建线程

多线程运行一定快吗?

当任务过多时,线程池会怎么办

有俩种可能:
1、如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
2、如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是 AbortPolicy

拒绝策略
1、AbortPolicy
直接丢弃任务,抛出RejectedExecutionException异常,是默认策略

2、CallerRunsPolicy
只用调用者所在的线程处理任务

3、DiscardOldestPolicy
丢弃等待队列中最旧的任务,并执行当前任务

4、DiscardPolicy
直接丢弃任务,但不抛出异常

想要让新建的线程有返回值怎么办

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

让各线程的共享变量不相互影响该怎么办

ThreadLocal

ThreadLocal结构是什么样的

两个或多个线程,操作一个共享变量,但任务没有返回,可能的原因

有一组共享资源,多个线程操作,任务没有返回,可能原因

怎么解决死锁

一、破坏请求和保持条件
• 方法一 —— 破坏“请求”条件(预先静态分配法)
每个进程执行之前,必须一次性地申请其在整个运行期间所需的全部资源,全部申请到了才能运行。这样它在整个运行过程中便不会再提出资源请求,从而破坏了“请求”条件。

缺点:
① 资源利用率很低:有些资源可能在最后才会用到,它却一直占用了那么久
② 进程可能出现饥饿现象:可能由于个别资源别其他进程占用而导致某进程迟迟不能开始

• 方法二 —— 破坏“保持”条件
每个进程提出申请资源前必须释放已占有的一切资源

二、破坏非抢占条件
• 方法一 —— 走不通就放弃自己的已有资源造福别人
进程 Pi 申请 Rj 类资源时,检查 Rj 中有无可用资源:有则分配给 Pi ;否则将 Pi 占有的资源全部释放而进入等待状态(Pi等待其原占有的所有资源和申请的资源)

• 方法二 —— 走不通先去抢别人的(前提是别人也走不通),抢不到就放弃自己的
当进程 Pi 申请 Rj 类型的资源时检查 Rj 中有无可用资源:有则分配给 Pi ;否则检查已获得 Rj 类资源的进程 Pk ,若 Pk 处于等待资源状态,则抢占 Pk 的 Rj 类资源并分配给 Pi,若 Pk 不处于等待资源状态,则置 Pi 于等待资源状态(此时Pi原已占有的资源可能被抢占)

这两种方法的缺点:
① 有的资源是不可抢占资源,比如打印机,被抢占后可能导致前一阶段的工作失效
② 延长了周转时间,降低了系统吞吐量,增加了系统开销:因为某些进程的执行可能会被无限推迟

三、破坏循环等待条件(有序资源使用法)
给系统中的所有资源类型进行排序编号
• 每个进程只能按递增顺序申请资源,即进程申请了序号为 8 的资源后,下次只能申请序号为 9 或以上资源
• 如果进程需要同一资源类型的多个实例(也就是序号相同的资源),则必须对它们一起进行申请
• 如果进程后面又想申请序号低的资源(比如5),那就必须把现在拥有的序号为5及其以上的资源全部释放

为什么这种规则可以破坏循环等待条件?
核心: 每个进程只能按递增顺序申请资源
因此每个时刻总有一个进程占据了较高序号的资源,那么它后面继续申请的资源一定是空闲的,这就保证了进程是可以一直向前推进的
优点:
与前两种策略相比,其资源利用率和系统吞吐量都有明显的改善

缺点:
① 序号必须相对稳定,这就限制了新设备的增加
② 如果作业使用各类资源的顺序与系统规定的递增顺序不符合的话,就会造成资源的浪费
③ 按规定次序申请资源的方法会限制用户简单、自主地编程

Java怎么实现内存回收?怎么判断对象能否回收,为什么不用引用计数法,jdk1.8默认垃圾回收器是什么

垃圾回收器
引用计数法
可达性分析
引用计数法很难解决对象之间相互循环引用的问题
可使用下述命令查看虚拟机参数

java -XX:+PrintCommandLineFlags -version

结果

-XX:InitialHeapSize=257905536 
-XX:MaxHeapSize=4126488576 
-XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers 
-XX:+UseCompressedOops 
-XX:-UseLargePagesIndividualAllocation 
-XX:+UseParallelGC
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

UseParallelGC指的是Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1

SpringBoot有什么特点或者优点

  • 简化依赖
    比如我们要创建一个 web 项目,使用 Spring 的朋友都知道,在使用 Spring 的时候,需要在 pom 文件中添加多个依赖,而 Spring Boot 则会帮助开发着快速启动一个 web 容器,在 Spring Boot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。
  • 简化配置
    Spring 虽然使Java EE轻量级框架,但由于其繁琐的配置,一度被人认为是“配置地狱”。各种XML、Annotation配置会让人眼花缭乱,而且配置多的话,如果出错了也很难找出原因。Spring Boot更多的是采用 Java Config 的方式,对 Spring 进行配置。
  • 简化部署
    在使用 Spring 时,项目部署时需要我们在服务器上部署 tomcat,然后把项目打成 war 包扔到 tomcat里,在使用 Spring Boot 后,我们不需要在服务器上去部署 tomcat,因为 Spring Boot 内嵌了 tomcat,我们只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目。
    另外,也降低对运行环境的基本要求,环境变量中有JDK即可。
    = 简化监控
    我们可以引入 spring-boot-start-actuator 依赖,直接使用 REST 方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。但是 Spring Boot 只是个微框架,没有提供相应的服务发现与注册的配套功能,没有外围监控集成方案,没有外围安全管理方案,所以在微服务架构中,还需要 Spring Cloud 来配合一起使用。

SpringBoot怎么实现自动装配

什么是自动装配:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能
@EnableAutoConfiguration:实现自动装配的核心注解
EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。
Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

有关注过SpringBoot新版本的特性吗

重磅!Spring Boot 2.7 正式发布,一大波新特性,看完我彻底躺平了。。

网站输入url,会发生什么

dns 三次握手四次挥手,http请求,浏览器渲染

有了解Http3.0吗,底层协议是什么

QUIC(Quick UDP Internet Connection)

有了解其他中间件吗,消息队列有哪些,项目为什么选择kafka

你对你投递岗位的认识或者你的优势

9.15字节二面

知不知道LRU,用什么数据结构实现,jdk里面有没有现成的数据结构(LinkedHashMap)

双向链表加hashmap

自定义String能不能被加载,双亲委派机制,各种类加载器的加载范围

不能,因为双亲委派模型,会让bootstrapclassloader加载类
BootStrapClassLoader的实例范围在sun.boot.class.path中
ExtClassLoader的实例范围在java.ext.dirs中
AppClassLoader的实例范围在java.class.path中

volatile了解吗,如果多个线程对某个加了volatile的数++操作,会有问题吗

有,i++不是原子操作

怎么避免上述问题


使用atomicInteger类

原子类的原理

其底层主要运用了unsafe类和cas思想(循环比较并替换).

介绍下自定义线程池,核心参数有哪些

线程池好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      ThreadFactory threadFactory,
                      RejectedExecutionHandler handler)
  • corePoolSize : 核心线程数定义了最小可以同时运行的线程数量。
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
  • keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
  • unit : keepAliveTime 参数的时间单位。
  • threadFactory :executor 创建新线程的时候会用到。
  • handler :饱和策略
  1. ThreadPoolExecutor.AbortPolicy: 抛出 RejectedExecutionException来拒绝新任务的处理。
  2. ThreadPoolExecutor.CallerRunsPolicy: 调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
  3. ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
  4. ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。

KeepAlive参数是做什么的,从什么时候开始计时

当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;

来了一个请求,查询id,如何从很多id中判断哪些存在哪些不存在

布隆过滤器。

bitmap怎么实现的,在java中实现应该用什么数据结构(提示说int数组占用空间过大,应该用bit)

bit数组(BitSet?)

场景题,从20亿数据中挑选top10000数据,怎么处理

建立小顶堆,10000,然后添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个。建堆时间复杂度是O(mlogm),算法的时间复杂度为O(nmlogm)(n为10亿,m为10000)

从很多数中取排序后的10000个数据(快排+剪枝)

堆排序更好

java怎么设置最大堆容量跟初始堆容量,怎么设置线程参数

初始堆内存
-Xms<heap size>[unit] 
最大堆内存
-Xmx<heap size>[unit]

新生代内存
-XX:NewSize=<young size>[unit]   最小
-XX:MaxNewSize=<young size>[unit]   最大
-Xmn256m 最小与最大相同

新生代与老年代比值  老年除以新生代
-XX:NewRatio = 1

-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen

-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen

串行垃圾收集器
并行垃圾收集器
CMS垃圾收集器
G1垃圾收集器

-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParNewGC
-XX:+UseG1GC

-Xss: 设置线程的最大栈空间,栈空间越大,方法的递归深度越大
-XX:+PrintGCDetails 打印GC详细信息(JDK8,9,10建议使用-Xlog:gc*)

判断题

List<Long> longList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>(); 
List<String> strings = new LinkedList<>(); 
longList.add(1L); integerList.add(1); strings.add("a"); 
System.out.println(longList.getClass() == integerList.getClass()); System.out.println(longList.getClass() == strings.getClass());

true false
longList与integerList都是ArrayList
strings是LinkedList

讲一下mysql分页语句,如果查询分页的index 10000到之后的数据,可能出现什么问题,怎么解决

select * from tablename limit index,pageNum;

select * from tablename limit  pageNum offset index;

假设数据表的id是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询:

select * from orders_history where type=2 
and id between 1000000 and 1000100 limit 100;
select * from orders_history where id >= 1000001 limit 100;

有联合索引(a,b,c),select * from table where b > 123 and a like ‘aaa%’; 走不走索引

只走a索引,b走不到
mysql 会一直向右匹配直到遇到范围查询 (>、<、between、like) 就停止匹配(包括 like ‘陈%’ 这种)

算法题

input
7
3 3 4 7 5 6 8
output
4
2 3 5 6
输入数组长度及数组,找到连续递增的最长子序列长度以及下标数组

9.15中新赛克一面

分布式锁怎么使用的

介绍下spring和springboot

@Autowired怎么实现自动注入bean

@Autowired注解按照类型去IOC容器中查找bean对象,如果容器(即为IOC容器)中没有该类型对象(例如,该类型是个接口),则去容器中查找该类型的子类(实现类)的对象,进行注入

当使用该注解时,Spring容器自动匹配bean(类对象),首先根据class后面的全限定名进行自动匹配(byType方式);其次就是根据id属性进行自动匹配,id的值是setXx方法中的Xx(byName方式)。
它和@Resource注解的原理相反,@Resource注解先通过byName的方式查找,再通过byType的方式,如果都不成功就会报错。

怎么让spring管理第三方jar包中的类

注解装配Bean

使用@Component等派生注解

@Bean定义方式

@Data
public class ConfigDemoBean {
}

@Configuration
public class BeanLoadConfig {
    @Bean
    public ConfigDemoBean configDemoBean() {
        return new ConfigDemoBean();
    }
}

引入外部jar包的Bean

@ComponentScan

/**
 * @Description: Springboot 启动类
 */
@ComponentScan(basePackages ={"com.third.bean"})
@SpringBootApplication()
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

@Import注解

@Import(value= com.third.bean.ThirdComponentBean.class)
@SpringBootApplication()
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

spring.factories
我们只需要在将配置放在第三方jar指定的文件中即可,使用者会自动加载,从而避免的代码的侵入

  • 在资源目录下新建目录 META-INF
  • 在 META-INF 目录下新建文件 spring.factories
  • 在文件中添加下面配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.third.bean.ConfigurationBean

SpringBoot引入第三方jar的Bean的三种方式

spring 一次反射就能注入bean吗,比如a中有b,b中有a,怎么解决

三级缓存解决循环依赖

三级缓存能解决所有循环依赖吗

不能解决构造器的循环依赖
不能解决 prototype 作用域循环依赖
不能解决多例的循环依赖
Spring 循环依赖及三级缓存

spring如何配置bean,比如有一个地方项目需要注入某个对象,另一个地方不需要注入,该怎么配置

mybatis也有两级缓存,了解过吗

MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存
开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询

当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库
MyBatis 二级缓存全详解

mybatis中的#与$区别

MyBatis处理 #{ } 占位符,使用的 JDBC 对象是PreparedStatement 对象,执行sql语句的效率更高。

使用PreparedStatement 对象,能够避免 sql 注入,使得sql语句的执行更加安全。

#{ } 常常作为列值使用,位于sql语句中等号的右侧;#{ } 位置的值与数据类型是相关的。

MyBatis处理 ${ } 占位符,使用的 JDBC 对象是 Statement 对象,执行sql语句的效率相对于 #{ } 占位符要更低。

${ } 占位符的值,使用的是字符串连接的方式,有 sql 注入的风险,同时也存在代码安全的问题。

${ } 占位符中的数据是原模原样的,不会区分数据类型。

${ } 占位符常用作表名或列名,这里推荐在能保证数据安全的情况下使用 ${ }。

项目怎么做的分页,pagehelper使用(没怎么听清,也不会)

redis除了缓存与锁还能做什么,讲一下你理解的redis

缓存之外,也经常用来做分布式锁,甚至是消息队列
Redis 5.0 新增加的一个数据结构 Stream 可以用来做消息队列,Stream 支持:

发布 / 订阅模式
按照消费者组进行消费
消息持久化( RDB 和 AOF)

Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向

怎么保证redis与数据库的同步,如果同时插入与查找,怎么办

redis持久化了解吗,讲一下,项目中怎么使用的

RDB与AOF

java参数传递 如果有个user对象,a方法中传入user对象,a中在new 一个对象并赋给user,外部的user会变吗

不会变

讲一下线程安全与线程不安全

线程不安全:在随机调度之下,线程执行有多种可能,其中某些可能会导致代码出bug就称为线程不安全。
线程不安全的原因

  • 操作系统的随机调度/抢占式执行(万恶之源)–>无法改变

  • 多个线程修改同一个变量(一个字都不能少)—>尽量避免

  • 有些修改操作,不是原子的!(不可拆分的最小单位,就叫原子 即对应一条机器指令)—>通过加锁操作,把指令打包

  • 内存可见性问题(内存改了,但是在优化的背景下,读不到,看不见)
    如:线程1一直在读取硬盘上的资源再判断,在多次读取硬盘并获得相同的结果后编译器会认为这样的做法过于低效,然后就会省略读取的过程,一直判断,若此时有线程2需要这个判断结果(读取到数据后的判断结果)就会出现问题.

  • 指令重排序(也是编译器,操作系统等的优化,调整了代码的执行顺序)

线程池了解过吗,讲一下核心参数

如果核心线程数为5,最大线程数10,阻塞队列5,第五个线程来的话,池里有几个线程,第六个呢

如果线程有优先级,怎么让线程池按优先级从阻塞队列中取任务,每次取都排序合理吗

用堆做阻塞队列 PriorityBlockingQueue

怎么获取线程返回值,用future的话怎么确定获取返回值的时间

通过FutureTask类实现,通过实现Callable接口,使用FutureTask启动子线程,通过FutureTask的get()方法即可精准的获取返回值
FutureTask的get方法可以设置超时时间

spring怎么实现事务,什么情况下事务会失效

@Transactional

  • 数据库存储引擎不支持
  • 未指定RollbackOn,且抛出的异常并非RuntimeException
  • 同一个类中调用事务方法
  • 非公开方法上的事务

除了注解还有什么方式能实现事务

编程式事务管理使用TransactionTemplate

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional 注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别

介绍下jvm

常量池在哪里

Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中

jvm参数用过吗,jstack,jps等

jps: 跟linux的ps一样,只不过是列出java程序
  jps -m  列出所有java程序,并显示传入参数 
  jps -l  列出所有java程序,显示类的全限名
jstat:观察java程序运行时的相关信息,主要是堆信息
  jstat -class -t pid 1000 2  查看classLoader相关信息,每隔一秒执行一次,总共收集两次
  jstat -gc pid 查看gc情况
  jstat -gcnew pid  查看新生代的详细信息
  jstat -gcold pid  查看老年代的详细信息
jinfo:查看java应用程序的扩展参数,部分参数可支持动态修改
  jinfo -flag MaxtenuringThreshold pid 查看gc升级年龄
  jinfo -flag +PrintGCDetails pid  修改使用PrintGCDetails参数
jmap:导出堆到文件
  jmap -histo pid > /usr/local/tmp/a.txt  java程序的对象统计信息
  jmap -dump:format=b file=/usr/local/heap.hprof PID 得到java程序的当前快照,
  主要用于分析线程的运行情况
jstack:查看线程堆栈
  jstack -l pid > /usr/local/tmp/stack.hprof 这里-l是打印锁的详细信息然后
  输出到指定目录的*.hprof文件中

9.15顺丰二面

分布式锁的使用

讲下java的int与Integer

基本类型与包装类型

讲一下hashmap,为什么扩容是2的倍数

hash值用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”

取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率

可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低

介绍下jvm中的younggc与fullgc

大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC
YGC时,对Eden区和From Survivor区的存活对象进行处理,如果满足动态年龄判断的条件或者To Survivor区空间不够则直接进入老年代,如果老年代空间也不够了,则会发生promotion failed,触发老年代的回收。否则将存活对象复制到To Survivor区。

此时Eden区和From Survivor区的剩余对象均为垃圾对象,可直接抹掉回收。

此外,老年代如果采用的是CMS回收器,为了减少CMS Remark阶段的耗时,也有可能会触发一次YGC

当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括新生代和老年代。除此之外,还有以下4种情况也会触发FGC:

  • 老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发FGC。
  • 空间分配担保:在YGC之前,会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果小于,说明YGC是不安全的,则会查看参数 HandlePromotionFailure 是否被设置成了允许担保失败,如果不允许则直接触发Full GC;如果允许,那么会进一步检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于也会触发 Full GC。
  • Metaspace(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize 参数的指定值时,也会触发FGC。
  • System.gc() 或者Runtime.gc() 被显式调用时,触发FGC

对应的垃圾回收器或者你了解的垃圾回收器是什么

Serial、SerialOld、ParallelScavenger、ParNew、CMS在物理和逻辑上都分代。G1只在逻辑上分代,物理上不分代。ZGC、Shenandoah逻辑和物理上都不分代。Epsilon是jdk11提出debug使用的,不用考虑
Serial、ParallelScavenge、ParNew是用于年轻代的,SerialOld、ParallelOld、CMS是用于老年代的。所以产生了垃圾回收器几种常见的组合:Serial+SerialOld、ParallelScavenger+ParallelOld、ParNew+CMS
新生代一般标记复制,老年代一般标记整理

写下线程死锁场景(写了但是中间报错,没找出来,讲了讲思路)

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

介绍下聚簇索引

索引跟数据一起存放

一个表能有多个聚簇索引吗

只能有一个 聚簇索引的顺序就是数据的物理存储顺序

索引叶子节点是什么数据结构

双向链表

联合索引了解吗,为什么会有最左匹配原则

讲下Innodb隔离级别

读已提交与可重复读区别

可重复读第一次select会生成readview,那么update会生成吗

不会,update是当前读,会读取最新记录

介绍下redolog,binlog和undolog

讲下OSI七层模型

应用层协议有哪些

tcp与udp区别

讲一下四次挥手

四次挥手客户端发送ACK后处于什么状态

介绍下tcp拥塞控制

算法 合并两个有序数组

9.16携程一面

为什么要重写hashcode与equals(貌似hashcode计算方式答错了)

因为hashcode和equals都不重写,则存放对象时调用的是Object对象的这两个方法。
那么hashcode就是地址值,而equals也是比较的地址值

https的ssl层用的什么加密协议

SSL本身就是一个协议

https每次传输都是非对称加密吗

不是,第一次是,后面是对称加密

synchronized与volatile区别

新建线程的方式

继承Thread,重写Run方法
实现Runnable,重写Run方法
实现Callable,封装FutureTask对象,注入Thread

有100本书,每人每次只能拿1到5本,a先拿,b再拿,怎么样a才能拿到最后一本书

mybatis中有#和$,有什么区别

有三个线程,分别输出a,b,c,想让输出 abc cba abc cba。。。怎么设计?

如果让你设计一个查询余额的接口,该考虑哪些方面

让spring管理bean有哪几种方式

spring有一个单例对象,要保证线程安全,该怎么做

懒汉式,双重校验锁

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){
 
    }
    public static Singleton getInstance(){
        if(instance == null){ //外层的if判断:如果实例被创建直接return,不让线程再继续竞争锁
            //在没有创建实例时,多个线程已经进入if判断了
            //一个线程竞争到锁,其他线程阻塞等待
            synchronized (Singleton.class) {
                //内层的if判断,目的是让竞争失败的锁如果再次竞争成功的话判断实例是否被创建,创建释放锁return,没有则创建
                if(instance == null){ 
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

枚举

public enum Singleton4 {
    INSTANCE;
}

饿汉式

在类加载的时候就创建实例
这种方式是满足线程安全的(JVM内部使用了加锁,即多个线程调用静态方法,只有一个线程竞争到锁并且完成创建,只执行一次)

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){
 
    }
    public static Singleton getInstance(){
        return instance;
    }
}

静态内部类

public class Singleton3 {
    private Singleton3() {
    }
 
    private static class Holder {
        private static final Singleton3 INSTANCE = new Singleton3();
 
    }
 
    public static final Singleton3 getInstance() {
        return Holder.INSTANCE;
    }
 
}

使用容器类

public class Singleton5 {
    private static Map<String, Object> objectMap = new HashMap<>();
 
    public static void registerService(String key, Object instance) {
        if (!objectMap.containsKey(key)) {
            objectMap.put(key, instance);
        }
    }
 
    public static Object getService(String key) {
        return objectMap.get(key);
    }
}

你了解哪些设计模式

怎么查询端口是否可用

telnet 10.119.229.205 11201

netstat -anp |grep 3306

你了解的端口有哪些

21:FTP服务所开放的端口,用于上传、下载文件。  
22:SSH端口,用于通过命令行模式远程连接Linux服务器或vps。  
23:Telnet端口,用于Telnet远程登录服务器。  
25:SMTP服务所开放的端口,用于发送邮件。  
80:HTTP用于HTTP服务提供访问功能,例如,IIS、Apache、Nginx 等服务。您可以参阅、检查、TCP 80端口是否正常工作,排查80端口故障。  
110:POP3用于POP3 协议,POP3 是电子邮件收发的协议。  
143:IMAP用于IMAP(Internet Message Access Protocol)协议,IMAP 是用于电子邮件的接收的协议。  
443:HTTPS 用于HTTPS服务提供访问功能。HTTPS 是一种能提供加密和通过安全端口传输的一种协议。  
1433:SQL Server SQL Server的TCP 端口,用于供SQL Server对外提供服务。  
1434:SQL Server SQL Server的UDP端口,用于返回SQL Server使用了哪个 TCP/IP 端口。  1521:Oracle通信端口,服务器上部署了Oracle SQL需要放行的端口。  
3306:MySQL数据库对外提供服务的端口。  
3389:Windows Server Remote Desktop Services Windows Server Remote Desktop Services(远程桌面服务)端口,可以通过这个端口远程连接服务器  
8080:代理端口,同80端口一样,8080 端口常用于WWW代理服务,实现网页浏览。如果用了8080端口,访问网站或使用代理服务器时,需要在 IP 地址后面加上 :8080。安装Apache Tomcat服务后,默认服务端口为8080。  
137、138、139 NetBIOS协议137、138 为UDP端口,通过网上邻居传输文件时使用的端口。139通过这个端口进入的连接试图获得 NetBIOS/SMB 服务。NetBIOS协议常被用于Windows文件、打印机共享和Samba。

9.22字节番茄小说后端开发三面

算法题,leetcode301 重排链表

重排链表

飞书发起视频会议,有域名,怎么转换到ip的

DNS解析过程详解

网络中数据包的传输流程,具体。比如从大连到北京,是怎么传输的

场景题,飞书群里发红包,限制人数,限制总额,随机红包,给出设计方案

算法题 一个蚂蚁在12点的刻度上,可以向右走也可以向左走,走了n步最终回到12点,求有几种走法。让说思路(说了一个不对,没想出来,结束面试,寄)

9.23途虎后端开发一面

带场景算法题,简单

equals与==区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)

线程安全的集合

Hashtable
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
ConcurrentSkipListMap
ConcurrentLinkedQueue

线程状态

  1. New 新建状态(线程刚被创建,start方法之前的状态)

  2. Runnable 运行状态(得到时间片运行中状态)(Ready就绪,未得到时间片就绪状态)

  3. Blocked 阻塞状态(如果遇到锁,线程就会变为阻塞状态等待另一个线程释放锁)

  4. Waiting 等待状态(无限期等待)

  5. Time_Waiting 超时等待状态(有明确结束时间的等待状态)

  6. Terminated 终止状态(当线程结束完成之后就会变成此状态)

get与post区别

  1. get是从服务器上获取数据,post是向服务器传送数据。

  2. GET请求把参数包含在URL中,将请求信息放在URL后面,POST请求通过request body传递参数,将请求信息放置在报文体中。

  3. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。

  4. get安全性非常低,get设计成传输数据,一般都在地址栏里面可以看到,post安全性较高,post传递数据比较隐私,所以在地址栏看不到, 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。

  5. GET请求能够被缓存,GET请求会保存在浏览器的浏览记录中,以GET请求的URL能够保存为浏览器书签,post请求不具有这些功能。

  6. HTTP的底层是TCP/IP,GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。

  7. GET产生一个TCP数据包,对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);POST产生两个TCP数据包,对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据),并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

常用状态码

常用状态码

mysql索引数据结构

B+树

有哪几种索引

主键索引
二级索引:

  • 唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  • 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
  • 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
  • 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。

一张表最多几个索引(官方15个?一般10个)

官方16个

索引建立原则

  1. 选择合适的字段创建索引:
  • 不为 NULL 的字段 :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
  • 被频繁查询的字段 :我们创建索引的字段应该是查询操作非常频繁的字段。
  • 被作为条件查询的字段 :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
  • 频繁需要排序的字段 :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
  • 被经常频繁用于连接的字段 :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
  1. 被频繁更新的字段应该慎重建立索引。
    虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。

  2. 尽可能的考虑建立联合索引而不是单列索引。
    因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。

  3. 注意避免冗余索引 。
    冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。

  4. 考虑在字符串类型的字段上使用前缀索引代替普通索引。
    前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。

怎么分析慢查询,用什么语句

explain

介绍下join语句

union与union all知道吗

抽象类能加final吗

不能,抽象类就是用来继承的,加了final不能被子类继承

try catch finally中哪个可以省略

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally

catch中有return finally中也有return 会怎么样

1.首先,不管try…catch是否有异常或者有return,只要有finally,都是要执行的

2.当try有return 语句,没有产生异常时,执行到return语句时,会先算出return 表达式的值,并将其保存起来。注意,此时没有返回,只是计算表达式的值并保存起来,然后再去执行finally代码块,如果finally代码块有return 语句,程序执行到return语句,程序会提前结束,然后返回值,不会去执行try中的return

例如 try 中 有return a+b ,执行到这里只是将a+b的值保存起来。然后再去执行finally ,finally有return a+c ,那么程序执行到return a+c时,便会返回a+c的结果,不会再去执行try中的return.

3.当try有异常,catch有return语句时,程序执行到try中有异常的地方,异常被捕获,跳转到catch代码块,执行到return语句时,同样只是保存return 表达式的值,然后再去执行finally代码块。

4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

5、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值

redis应用场景

redis应用可能出现的问题

介绍下布隆过滤器

缓存穿透中,假如针对某一个redis key的穿透,怎么处理(哨兵监控,更新过期时间)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值