后端大厂面试总结大全三

1、京东面试题,商品列表需要并发展示商品信息,价格信息,库存信息,请实现伪代码

这个题重点考察了并发请求,一个页面需要请求几十个接口,那普通的不使用并发将会是响应非常的慢,京东要求响应时间在毫秒级,所以如果请求的数据量比较大,建议使用elasticsearch(通用),redis(适用查询条件少);同时我们要使用多线程来实现并发请求。

elasticsearch的使用这里不做介绍,重点讲解使用并发来提搞响应效率。

1.MyFutureTask实现类

内部定义一个线程池进行任务的调度和线程的管理以及线程的复用,大家可以根据自己的实际项目情况进行配置

其中线程调度示例:核心线程 8 最大线程 20 保活时间30s 存储队列 10 有守护线程 拒绝策略:将超负荷任务回退到调用者 说明 :
默认使用核心线程(8)数执行任务,任务数量超过核心线程数就丢到队列,队列(10)满了就再开启新的线程,新的线程数最大为20,当任务执行完,新开启的线程将存活30s,若没有任务就消亡,线程池回到核心线程数量.

import com.boot.lea.mybot.dto.UserBehaviorDataDTO;
import com.boot.lea.mybot.service.UserService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
 
import javax.annotation.Resource;
import java.util.concurrent.*;
 
/**
 * @author Lijing
 * @date 2019年7月29日
 */
@Slf4j
@Component
public class MyFutureTask {
 
    @Resource
    UserService userService;
 
    /**
     * 核心线程 8 最大线程 20 保活时间30s 存储队列 10 有守护线程 拒绝策略:将超负荷任务回退到调用者
     */
    private static ExecutorService executor = new ThreadPoolExecutor(8, 20,
            30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10),
            new ThreadFactoryBuilder().setNameFormat("User_Async_FutureTask-%d").setDaemon(true).build(),
            new ThreadPoolExecutor.CallerRunsPolicy());
 
    @SuppressWarnings("all")
    public UserBehaviorDataDTO getUserAggregatedResult(final Long userId) {
 
        System.out.println("MyFutureTask的线程:" + Thread.currentThread());
 
        long fansCount = 0, msgCount = 0, collectCount = 0,
                followCount = 0, redBagCount = 0, couponCount = 0;
 
//        fansCount = userService.countFansCountByUserId(userId);
//        msgCount = userService.countMsgCountByUserId(userId);
//        collectCount = userService.countCollectCountByUserId(userId);
//        followCount = userService.countFollowCountByUserId(userId);
//        redBagCount = userService.countRedBagCountByUserId(userId);
//        couponCount = userService.countCouponCountByUserId(userId);
 
        try {
 
            Future<Long> fansCountFT = executor.submit(() -> userService.countFansCountByUserId(userId));
            Future<Long> msgCountFT = executor.submit(() -> userService.countMsgCountByUserId(userId));
            Future<Long> collectCountFT = executor.submit(() -> userService.countCollectCountByUserId(userId));
            Future<Long> followCountFT = executor.submit(() -> userService.countFollowCountByUserId(userId));
            Future<Long> redBagCountFT = executor.submit(() -> userService.countRedBagCountByUserId(userId));
            Future<Long> couponCountFT = executor.submit(() -> userService.countCouponCountByUserId(userId));
 
            //get阻塞
            fansCount = fansCountFT.get();
            msgCount = msgCountFT.get();
            collectCount = collectCountFT.get();
            followCount = followCountFT.get();
            redBagCount = redBagCountFT.get();
            couponCount = couponCountFT.get();
 
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            log.error(">>>>>>聚合查询用户聚合信息异常:" + e + "<<<<<<<<<");
        }
        UserBehaviorDataDTO userBehaviorData =
                UserBehaviorDataDTO.builder().fansCount(fansCount).msgCount(msgCount)
                        .collectCount(collectCount).followCount(followCount)
                        .redBagCount(redBagCount).couponCount(couponCount).build();
        return userBehaviorData;
    }
 
}

这里的参数组合时使用了建造者模式;在UserBehaviorDataDTO添加Builder< T >类;然后添加参数属性,就可以实现上文的组装参数。

建造者模式

2.service业务方法

常规业务查询方法,为了特效,以及看出实际的效果,我们每个方法做了延时

import com.boot.lea.mybot.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.concurrent.TimeUnit;
 
@Service
public class UserServiceImpl implements UserService {
 
    @Autowired
    UserMapper userMapper;
 
    @Override
    public long countFansCountByUserId(Long userId) {
        try {
            Thread.sleep(10000);
            System.out.println("获取FansCount===睡眠:" + 10 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("UserService获取FansCount的线程  " + Thread.currentThread().getName());
        return 520;
    }
 
    @Override
    public long countMsgCountByUserId(Long userId) {
        System.out.println("UserService获取MsgCount的线程  " + Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
            System.out.println("获取MsgCount===睡眠:" + 10 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 618;
    }
 
    @Override
    public long countCollectCountByUserId(Long userId) {
        System.out.println("UserService获取CollectCount的线程  " + Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
            System.out.println("获取CollectCount==睡眠:" + 10 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 6664;
    }


 
    @Override
    public long countFollowCountByUserId(Long userId) {
        System.out.println("UserService获取FollowCount的线程  " + Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
            System.out.println("获取FollowCount===睡眠:" + 10+ "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userMapper.countFollowCountByUserId(userId);
    }
 
    @Override
    public long countRedBagCountByUserId(Long userId) {
        System.out.println("UserService获取RedBagCount的线程  " + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(4);
            System.out.println("获取RedBagCount===睡眠:" + 4 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 99;
    }
 
    @Override
    public long countCouponCountByUserId(Long userId) {
        System.out.println("UserService获取CouponCount的线程  " + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(8);
            System.out.println("获取CouponCount===睡眠:" + 8+ "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 66;
    }
}

3.controller调用

/**
 * @author LiJing
 * @ClassName: UserController
 * @Description: 用户控制器
 * @date 2019/7/29 15:16
 */
@RestController
@RequestMapping("user/")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @Autowired
    private MyFutureTask myFutureTask;
 
    @GetMapping("/index")
    @ResponseBody
    public String index() {
        return "启动用户模块成功~~~~~~~~";
    }
 
    //http://localhost:8080/api/user/get/data?userId=4
 
    @GetMapping("/get/data")
    @ResponseBody
    public UserBehaviorDataDTO getUserData(Long userId) {
        System.out.println("UserController的线程:" + Thread.currentThread());
        long begin = System.currentTimeMillis();
        UserBehaviorDataDTO userAggregatedResult = myFutureTask.getUserAggregatedResult(userId);
        long end = System.currentTimeMillis();
        System.out.println("===============总耗时:" + (end - begin) /1000.0000+ "秒");
        return userAggregatedResult;
    }
 
}

我们启动项目:开启调用 http://localhost:8080/api/user/get/data?userId=4

当我们线程池配置为:核心线程 8 最大线程 20 保活时间30s 存储队列 10 的时候,我们测试的结果如下:
在这里插入图片描述

结果:我们看到每个server method的执行线程都是从线程池中发起的线程名:User_Async_FutureTask-%d,
总耗时从累计的52秒缩短到10秒,即取决于最耗时的方法查询时间.

总结
使用FutureTask的时候,就是将任务runner以caller的方式进行回调,阻塞获取,最后我们将结果汇总,即完成了开启多线程异步调用我们的业务方法.

Future<Long> fansCountFT = executor.submit(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    return userService.countFansCountByUserId(userId);
                }
            });

异步编程小知识:

package com.boot.lea.mybot.futrue;
 
/**
 * @ClassName: TestFuture
 * @Description: 演示异步编程
 * @author LiJing
 * @date 2019/8/5 15:16
 */
@SuppressWarnings("all")
public class TestFuture {
    static ExecutorService executor = Executors.newFixedThreadPool(2);
 
    public static void main(String[] args) throws InterruptedException {
        //两个线程的线程池
        //小红买酒任务,这里的future2代表的是小红未来发生的操作,返回小红买东西这个操作的结果
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("爸:小红你去买瓶酒!");
            try {
                System.out.println("小红出去买酒了,女孩子跑的比较慢,估计5s后才会回来...");
                Thread.sleep(5000);
                return "我买回来了!";
            } catch (InterruptedException e) {
                System.err.println("小红路上遭遇了不测");
                return "来世再见!";
            }
        }, executor);
 
        //小明买烟任务,这里的future1代表的是小明未来买东西会发生的事,返回值是小明买东西的结果
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("爸:小明你去买包烟!");
            try {
                System.out.println("小明出去买烟了,可能要3s后回来...");
                Thread.sleep(3000);
 
                throw new InterruptedException();
//                return "我买回来了!";
            } catch (InterruptedException e) {
                System.out.println("小明路上遭遇了不测!");
                return "这是我托人带来的口信,我已经不在了。";
            }
        }, executor);
 
        //获取小红买酒结果,从小红的操作中获取结果,把结果打印
        future2.thenAccept((e) -> {
            System.out.println("小红说:" + e);
        });
        //获取小明买烟的结果
        future1.thenAccept((e) -> {
            System.out.println("小明说:" + e);
        });
 
        System.out.println("爸:等啊等 西湖美景三月天嘞......");
        System.out.println("爸: 我觉得无聊甚至去了趟厕所。");
        Thread.currentThread().join(9 * 1000);
        System.out.println("爸:终于给老子买来了......huo 酒");
        //关闭线程池
        executor.shutdown();
    }
}

在这里插入图片描述
总结:京东解决大量接口响应时间毫秒级的大楷的思路就是查询库层面使用elasticesearch,然后接口的层面使用多线程并发请求,然后将每个线程的结果集进行合并返回处理。

2、synchronized和Lock的区别

区别:
1、lock是一个接口,而synchronized是java的一个关键字。
2、synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。
3、关键字不同。
4、synchronized自行加锁和释放锁,而lock需要手动加锁和解锁。
5、synchronized与lock适用范围不同,lock只能用来修饰代码块,而synchronized既可以修饰代码块,又可以修饰静态方法和普通方法。
6、lock是java层面的锁的实现,synchronized是JVM层面的实现。
7、synchronized锁的模式只有非公平锁模式,而lock既可以使用公平锁又可以使用非公平锁的模式。
8、lock的灵活性更高(tryLock)

在这里插入图片描述
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。

使用场景:

1.使用synchronized 修饰代码块(可给人以对象进行加锁)
2.使用synchronized 来修饰静态方法(对当前的类进行加锁)
3.使用sychronized 来修饰普通方法(对当前类实例进行加锁)

**注意:**lock()操作一定要放在try外面,如果放在可能会造成两个问题:
1.如果try里抛出异常,还没有加锁成功就执行finaly里释放所操作了。因为还没有得到锁,就释放锁了。
2.如果放在try里面,如果没有锁的情况下试图释放锁,这个时候产生的异常就会将业务代码(try里内容异常)覆盖掉,增加代码检查难度。
3.如果一定要写在try里 ,一定要写在第一行。

3、io流中使用到的设计模式

使用到了装饰器模式和适配器模式

装饰器与适配器异同点:
装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一一样。
适配器模式:是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。
装饰器模式:不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的

使用的具体代码

4、虚拟内存和物理内存的关系

在计算机中,所有运行的程序都需要经过内存来执行,可以分为物理内存和虚拟内存,物理内存大小也就是指内存条的容量大小,由于物理内存容量比较小,很容易就塞满了各种应用与数据,为了解决这个问题,WINDOWS运用了虚拟内存技术,即拿出一部分硬盘空间做“内存”来弥补计算机RAM空间的缺乏。

5、MySQL日志有哪几种?以及使用场景

MySQL中存在着以下几种日志:重写日志(redo log)、回滚日志(undo log)、二进制日志(bin log)、错误日志(error log)、慢查询日志(slow query log)、一般查询日志(general log)

MySQL中的数据变化会体现在上面这些日志中,比如事务操作会体现在redo log、undo log以及bin log中,数据的增删改查会体现在 binlog 中。本章是对MySQL日志文件的概念及基本使用介绍,不涉及底层内容。针对开发人员而言,这几种日志中最有可能使用到的是慢查询日志

参考链接

6、Redis如何保证数据不丢失以及如何处理堆积数据

数据丢失问题处理方法:

Redis做持久化处理,Redis 的持久化主要有两大机制,即 AOF(Append Only File)日志和 RDB 快照

AOF
RDB

数据堆积处理方法:
在这里插入图片描述

7、list和map的初始化大小

list 会在第一次add 时进行初始化,如果不知道容量大小,默认大小是10。

map 初始化 默认是16 每次扩容都要增加一倍。 如果指定容量,如果容量的2的n次方 ,容量不变, 如果容量不是2的n次方比如是158 ,那么map 会替换成256 ,因为158 最靠近的2的n次方是25 6。

8、rabbitmq如何避免重复消费

消费者端实现幂等性,意味着消息永远不会消费多次,即使收到了多条一样的消息。通常有两种方式来避免消费重复消费:

方式1: 消息全局 ID 或者写个唯一标识(如时间戳、UUID 等) :每次消费消息之前根据消息 id
去判断该消息是否已消费过,如果已经消费过,则不处理这条消息,否则正常消费消息,并且进行入库操作。(消息全局 ID 作为数据库表的主键,防止重复)

方式2: 利用 Redis 的 setnx 命令:给消息分配一个全局 ID,消费该消息时,先去 Redis
中查询有没消费记录,无则以键值对形式写入 Redis ,有则不消费该消息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bst@微胖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值