目录
一.集合
(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扩容机制的简单总结
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() ⽅法会阻塞当前线程直到任务完成,⽽使⽤ get(long 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.MyISAM和InnoDB区别
MVCC MySQL-InnoDB-MVCC多版本并发控制
2. 锁与事务
Mysql之锁与事务
3. 高级
Mysql高级
(2) Redis
1. redis 的线程模型
⽂件事件处理器的结构包含 4 个部分:
- 多个 socket
- IO 多路复⽤程序
- ⽂件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产⽣不同的操作,每个操作对应不同的⽂件事件,但是 IO 多路复⽤程序会监听多个 socket ,会将 socket 产⽣的事件放⼊队列中排队,事件分派器每次从队列中取出⼀个事件,把该事件交给对应的事件处理器进⾏处理。
2. redis 内存淘汰机制
3. redis 持久化机制
4. 缓存雪崩和缓存穿透问题解决⽅案
解决办法
1)缓存⽆效 key 表名:列名:主键名:主键值 尽量将⽆效的 key的过期时间设置短⼀点⽐如 1 分钟