面试题(二)

目录

一.集合

(1) LinkedHashMap(JDK 1.8)

(2) ArrayList

(3) HashMap

(4) ConcurrentHashMap

二. IO流

(1) 分类

(2) 应用

三.多线程

(1) 线程状态

(2) 死锁

(3) 应用

(4) 要点

(5) 线程池

返回

ThreadPoolExecutor 类分析

线程池原理分析

四. 数据库

(1) Mysql

1.MyISAM和InnoDB区别

2. 锁与事务

3. 高级

(2) Redis

1. redis 的线程模型

2. redis 内存淘汰机制

3. redis 持久化机制

4. 缓存雪崩和缓存穿透问题解决⽅案


一.集合

(1) LinkedHashMap(JDK 1.8)

鸿蒙OS LinkedHashMap

分析 java.util.LinkedHashMap  

LinkedHashMap 源码详细分析(JDK1.8)

Set集合元素不重复原因

集合不包含e1.equals(e2)的元素对e1和e2 ,并且最多包含一个空元素; 

更正式地说,如果集合不包含元素e2 ,则将指定的元素e添加到该集合中,使得(e==null ? e2==null : e.equals(e2)) 。如果此集合已包含该元素,则调用将保持集合不变并返回false 。结合对构造函数的限制,这确保了集合永远不会包含重复的元素。 

(2) ArrayList

ArrayList扩容机制的简单总结

【java 8】ArrayList的初始容量以及扩容测试 

https://www.cnblogs.com/ruoli-0/p/13714389.html (重点)

ArrayList的特点:

  1.ArrayList的底层数据结构是数组,所以查找遍历快,增删慢。

  2.ArrayList可随着元素的增长而自动扩容,正常扩容的话,每次扩容到原来的1.5倍。

  3.ArrayList的线程是不安全的。

ArrayList的扩容:

  扩容可分为两种情况:

  第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList在扩容时略有不同:

    1.无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。

    2.传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

    3.传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

  第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。

(3) HashMap

Java 8系列之重新认识HashMap

HashMap集合技术点

Java HashMap的死循环 

(4) ConcurrentHashMap

ConcurrentHashMap实现原理及源码分析

JDK1.8 Node 数组+链表+红⿊树的数据结构来实现 synchronized 和 CAS 来操作

二. IO流

(1) 分类

 Java IO(面试题)

(2) 应用


    @Override
    public String importExcel(MultipartFile file) {
        try {
            String content = new String(file.getBytes(), "gb2312");
            //获取表头字段
            String csvheader = content.split("\r")[0];
            InputStreamReader reader = getDestContent(content);
            List<SppiImportEntity> entitylist = CsvUtil.getImportData(reader,
                    SppiImportEntity.class);
            //临时表导入数据校验
            checkImportTmpData(entitylist, csvHeader);
            //2.导数据到临时表
            mktCbSppiMapper.insertTempList(entityList, 40000);
            //3.插入正式表
            //将临时表数据插入到正式表mkt_cb_sppi
            //导入用户
            String importUser = SessionUserHolder.getsessionUser().setusercode();
            String now = LocalDateTime.now().format(DeteTimeFormatter.ofPattern("yyyy-M-dd HH:mm:ss"));
            mktcbSppiMapper.insertSppiList(fileTp, importuser, now);
        } catch (Exception e) {
            e.getMessage();
        }
    }

    Pattern pattern = Pattern.compile("(\\d)(,)(\\d)");
    /**
     * 获取处理后目标字符串的输入流
     * @return 处理后字符申的输入流
     * @throws IOException
     * @param  content 要处理的字符串
     */
    private InputStreamReader getDestContent(String content) throws IOException {
        StringBuilder destString = new StringBuilder();
        Arrays.asList(content.split("\n")).stream().forEach(s -> {
            StringBuilder stringBuilder = new StringBuilder(s);

            Matcher matcher = pattern.matcher(s);
            //该行字符串英文逗号已被替换掉的次数
            int count = 0;
            while (matcher.find()) {
                //去除英文逗号
                stringBuilder.replace(matcher.start(2) - count, matcher.end(2) - count, "");
                count++;
            }
            destString.append(stringBuilder.toString() + "\n");
        });
        byte[] bytes = destString.toString().getBytes("gb2312");
        InputStream inputstream = new ByteArrayInputStream(bytes);
        InputStreamReader inputStreamReader = new InputStreamReader(inputstream, "gb2312");
        return inputStreamReader;
    }

三.多线程

(1) 线程状态

(2) 死锁

(3) 应用

java.util.concurrent.Callable;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;

import java.util.List;
import java.util.concurrent.Callable;


@Slf4j
public class InfoPublisherThread implements Callable<List<InfoPublisherRepVO>> {

    private KafkaTemplate<String, String> kafkaTemplate;
    private InfoPublisherApiMapper mapper;
    private int page;//分页index
    private int size;//数量

    public InfoPublisherThread(KafkaTemplate<String, String> kafkaTemplate, InfoPublisherApiMapper mapper, int page, int size) {
        this.kafkaTemplate = kafkaTemplate;
        this.mapper = mapper;
        this.page = page;
        this.size = size;
    }


    @Override
    public List<InfoPublisherRepVO> call(){
        log.info("单次5w条推送任务开始执行");
        long start = System.currentTimeMillis();
        List<InfoPublisherRepVO> list = mapper.pageList(page, size);
        list.forEach(repVO -> {
            kafkaTemplate.send(TopicConstant.INFO_PUBLISHER_TOPIC, JSON.toJSONString(repVO));
        });
        long end = System.currentTimeMillis();
        log.info("单次5w条推送任务耗时:{}",(end-start)/1000);
        return list;
    }

}

java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

    public void batchDeal(List<InfoSecurityCustomType> models, int groupSize, BaseEntityEnum baseEntityEnum) {
        List<List<InfoSecurityCustomType>> groupList = ArrayUtils.splitGroup(models, groupSize);
        // 创建线程数 = 数据总数 / groupList
        int threadPoolSize = groupList.size();
        // 最多创建 4 个线程
        threadPoolSize = threadPoolSize >= 4 ? 4 : threadPoolSize;
        ThreadPoolExecutor executor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize * 10, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        try {
            for (int i = 0, length = groupList.size(); i < length; i++) {
                List<InfoSecurityCustomType> list = groupList.get(i);
                switch (baseEntityEnum) {
                    case ADD:
                        InfoSecurityCustomTypeBatchSaveThread saveThread = new InfoSecurityCustomTypeBatchSaveThread(list, mapper,kafkaTemplate);
                        executor.execute(saveThread);
                        break;
                    case UPDATE:
                        InfoSecurityCustomTypeBatchUpdateThread updateThread = new InfoSecurityCustomTypeBatchUpdateThread(list, mapper);
                        executor.execute(updateThread);
                        break;
                    case DELETE:
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        } finally {
            executor.shutdown();
        }
    }

(4) 要点

1. 为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地⽅法栈是线程私有的

2. 并发: 同⼀时间段,多个任务都在执⾏ (单位时间内不⼀定同时执⾏)

    并⾏: 单位时间内,多个任务同时执⾏。
3. 多线程问题:  内存泄漏、上下⽂切换、死锁还有受限于硬件和软件的资源闲置问题
4.  当前任务在执⾏完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次
再切换回这个任务时,可以再加载这个任务的状态。 任务从保存到再加载的过程就是⼀次上下⽂切换
5.  调⽤ start ⽅法⽅可启动线程并使线程进⼊就绪状态,⽽ run ⽅法只是 thread 的⼀个普通
⽅法调⽤,还是在主线程⾥执⾏。
6.  sleep() ⽅法和 wait() ⽅法区别和共同点
  • 两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁
  • 两者都可以暂停线程的执⾏。
  • Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。
  • wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者
  • notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long
  • timeout)超时后线程会⾃动苏醒

(5) 线程池

返回

  • Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝可以。
  • execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;
  • submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象,通过这Future 对象可以判断任务是否执⾏成功,并且可以通过 Future的 get() ⽅法来获取 返回值,get() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ getlong timeout, TimeUnit unit) ⽅法则会阻塞当前线程⼀段时间后⽴即返回,这时候有可能任务没有执⾏完。

ThreadPoolExecutor 类分析

 /**
 * ⽤给定的初始参数创建⼀个新的ThreadPoolExecutor。
 */
 public ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize,
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue<Runnable> workQueue,
 ThreadFactory threadFactory,
 RejectedExecutionHandler handler) {
 if (corePoolSize < 0 ||
 maximumPoolSize äã 0 ||
 maximumPoolSize < corePoolSize ||
 keepAliveTime < 0)
 throw new IllegalArgumentException();
 if (workQueue WX null || threadFactory WX null || handler WX
null)
 throw new NullPointerException();
 this.corePoolSize = corePoolSize;
 this.maximumPoolSize = maximumPoolSize;
 this.workQueue = workQueue;
 this.keepAliveTime = unit.toNanos(keepAliveTime);
 this.threadFactory = threadFactory;
 this.handler = handler;
 }

ThreadPoolExecutor 构造函数重要参数分析
ThreadPoolExecutor 3 个最重要的参数:
corePoolSize : 核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。
maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数 量变为最⼤线程数。
workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达到 的话,新任务就会被存放在队列中。
ThreadPoolExecutor 其他常⻅参数 :
1. keepAliveTime : 当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任
务提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了
keepAliveTime 才会被回收销毁;
2. unit : keepAliveTime 参数的时间单位。
3. threadFactory :executor 创建新线程的时候会⽤到。
4. handler : 饱和策略。关于饱和策略下⾯单独介绍⼀下。
ThreadPoolExecutor 饱和策略
ThreadPoolExecutor 饱和策略定义 :
如果当前同时运⾏的线程数量达到最⼤线程数量并且队列也已经被放满了任务时, ThreadPoolTaskExecutor 定义⼀些策略 :
ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的处理。
ThreadPoolExecutor.CallerRunsPolicy :调⽤执⾏⾃⼰的线程运⾏任务。您不会任务请
求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应⽤程序可以承受此延迟并且你不能任务丢弃任何⼀个任务请求的话,你可以选择这个策略。
ThreadPoolExecutor.DiscardPolicy 不处理新任务,直接丢弃掉。
ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求。
举个例⼦: Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor
的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使⽤的是 ThreadPoolExecutor.AbortPolicy 。在默认情况下, ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代
表你将丢失对这个任务的处理。 对于可伸缩的应⽤程序,建议使⽤ ThreadPoolExecutor.CallerRunsPolicy 。当最⼤池被填满时,此策略为我们提供可伸缩队列。
public class ThreadPoolExecutorDemo {
 private static final int CORE_POOL_SIZE = 5;
 private static final int MAX_POOL_SIZE = 10;
 private static final int QUEUE_CAPACITY = 100;
 private static final Long KEEP_ALIVE_TIME = 1L;
 public static void main(String[] args) {
 //使⽤阿⾥巴巴推荐的创建线程池的⽅式
 //通过ThreadPoolExecutor构造函数⾃定义参数创建
 ThreadPoolExecutor executor = new ThreadPoolExecutor(
 CORE_POOL_SIZE,
 MAX_POOL_SIZE,
 KEEP_ALIVE_TIME,
 TimeUnit.SECONDS,
 new ArrayBlockingQueue<>(QUEUE_CAPACITY),
 new ThreadPoolExecutor.CallerRunsPolicy());
 for (int i = 0; i < 10; i++) {
 //创建WorkerThread对象(WorkerThread类实现了Runnable 接⼝)
 Runnable worker = new MyRunnable("" + i);
 //执⾏Runnable
 executor.execute(worker);
 }
//终⽌线程池
 executor.shutdown();
 while (!executor.isTerminated()) {
 }
 System.out.println("Finished all threads");
 }

线程池原理分析

四. 数据库

(1) Mysql

1.MyISAMInnoDB区别

MVCC   MySQL-InnoDB-MVCC多版本并发控制

2. 锁与事务

Mysql之锁与事务

3. 高级

Mysql高级

(2) Redis

1. redis 的线程模型

redis 内部使⽤⽂件事件处理器 file event handler ,这个⽂件事件处理器是单线程的,所以
redis 才叫做单线程的模型。它采⽤ IO 多路复⽤机制同时监听多个 socket ,根据 socket 上的事件
来选择对应的事件处理器进⾏处理。
⽂件事件处理器的结构包含 4 个部分:
  • 多个 socket
  • IO 多路复⽤程序
  • ⽂件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产⽣不同的操作,每个操作对应不同的⽂件事件,但是 IO 多路复⽤程序会监听多个 socket ,会将 socket 产⽣的事件放⼊队列中排队,事件分派器每次从队列中取出⼀个事件,把该事件交给对应的事件处理器进⾏处理。

2. redis 内存淘汰机制

3. redis 持久化机制

持久化数据也就是将内存中的数据写⼊到硬盘
Redis ⼀种持久化⽅式叫 快照(snapshotting RDB ),
另⼀种⽅式是只追加⽂件( append-only file,AOF

4. 缓存雪崩和缓存穿透问题解决⽅案

缓存雪崩
简介:缓存同⼀时间⼤⾯积的失效,所以,后⾯的请求都会落到数据库上,造成数据库短时间内承受⼤量请求⽽崩掉。

缓存穿透
缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这⼀层。举个例⼦:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量请求,导致⼤量请求落到数据库。

解决办法

1)缓存⽆效 key 表名:列名:主键名:主键值   尽量将⽆效的 key的过期时间设置短⼀点⽐如 1 分钟

2 )布隆过滤器   非法Key过滤        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值