1、Tomcat的工作线程是基于线程池的,线程池会重用固定的几个线程,一旦线程重用,那么很可能首次从ThreadLocal获取的值是之前其他用户的请求遗留的值。这时,ThreadLocal中的用户信息就是其他用户的信息。使用类似ThreadLocal工具来存放一些数据时,需要特别注意在代码运行完后,显式地去清空设置的数据
2、大量写的场景(10万次add操作),CopyOnWriteArray几乎比同步的ArrayList慢一百倍;在大量读的场景下(100万次get操作),CopyOnWriteArray又比同步的ArrayList快五倍以上
3、静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护
4、即使我们确实有一些共享资源需要保护,也要尽可能降低锁的粒度,仅对必要的代码块甚至是需要保护的资源本身加锁
5、如果业务逻辑中锁的实现比较复杂的话,要仔细看看加锁和释放是否配对,是否有遗漏释放或重复释放的可能性;并且对于分布式锁要考虑锁自动超时释放了,而业务逻辑却还在进行的情况下,如果别的线程或进程拿到了相同的锁,可能会导致重复执行。
6、线程池默认的工作行为:
不会初始化corePoolSize个线程,有任务来了才创建工作线程;
当核心线程满了之后不会立即扩容线程池,而是把任务堆积到工作队列中;
当工作队列满了后扩容线程池,一直到线程个数达到maximumPoolSize为止;
如果队列已满且达到了最大线程后还有任务进来,按照拒绝策略处理;
当线程数大于核心线程数时,线程等待keepAliveTime后还是没有任务需要处理的话,收缩线程到核心线程数
7、要根据任务的“轻重缓急”来指定线程池的核心参数,包括线程数、回收策略和任务队列:
对于执行比较慢、数量不大的IO任务,或许要考虑更多的线程数,而不需要太大的队列。
而对于吞吐量较大的计算型任务,线程数量不宜过多,可以是CPU核数或核数*2(理由是,线程一定调度到某个CPU进行执行,如果任务本身是CPU绑定的任务,那么过多的线程只会增加线程切换的开销,并不能提升吞吐量),但可能需要较长的队列来做缓冲。
8、JedisPool是线程安全的连接池,Jedis是非线程安全的单一连接。知道了原理之后,我们再使用Jedis就胸有成竹了。如果多个线程在执行操作,那么既无法确保整条命令以一个原子操作写入Socket,也无法确保写入后、读取前没有其他数据写到远端
9、对类似数据库连接池的重要资源进行持续检测,并设置一半的使用量作为报警阈值,出现预警后及时扩容。
9、连接超时参数ConnectTimeout,让用户配置建连阶段的最长等待时间;通常可以认为出现连接超时是网络问题或服务不在线,而出现读取超时是服务处理超时
读取超时参数ReadTimeout,用来控制从Socket上读取数据的最长等待时间。读取超时指的是,向Socket写入数据后,我们等待Socket返回数据的超时时间
通过ribbon发送get请求,会有自动重试的机制,可通过设置:ribbon.MaxAutoRetriesNextServer=0,或者改为post请求。
10、spring事务@Transaction生效的前提:
1)除非特殊配置(比如使用AspectJ静态织入实现AOP),否则只有定义在public方法上的@Transactional才能生效
2)必须通过代理过的类从外部调用目标方法才能生效
11、强烈建议你在开发时打开相关的Debug日志,以方便了解Spring事务实现的细节,并及时判断事务的执行情况。通过JPA访问数据库可通过logging.level.org.springframework.orm.jpa=DEBUG打开
12、事务即便生效也不一定能回滚
通过AOP实现事务处理可以理解为,使用try…catch…来包裹标记了@Transactional注解的方法,当方法出现了异常并且满足一定条件的时候,在catch里面我们可以设置事务回滚,没有异常则直接提交事务。
这里的“一定条件”,主要包括两点。
第一,只有异常传播出了标记了@Transactional注解的方法,事务才能回滚。(或者手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();)
第二,默认情况下,出现RuntimeException(非受检异常)或Error的时候,Spring才会回滚事务。(可通过@Transactional(rollbackFor = Exception.class)检测所有异常)
13、出了异常事务不一定回滚;不出异常,事务也不一定可以提交
14、聚簇索引、二级索引、B+树、回表、成本计算
B+树的特点包括:
最底层的节点叫作叶子节点,用来存放数据;
其他上层节点叫作非叶子节点,仅用来存放目录项,作为索引;
非叶子节点分为不同层次,通过分层来降低每一层的搜索量;
所有节点按照索引键大小排序,构成一个双向链表,加速范围查找。
15、额外创建二级索引的代价:
1)维护代价
2)空间代价
3)回表的代价
16、索引失效的情况:
1)索引只能匹配列前缀
2)条件涉及函数操作无法走索引
3)联合索引只能匹配左边的列
17、对于自定义的类型,如果要实现Comparable,请记得equals、hashCode、compareTo三者逻辑一致。
18、使用BigDecimal表示和计算浮点数,请务必使用字符串的构造方法来初始化BigDecimal:
浮点数的字符串格式化也要通过BigDecimal进行
19、BigDecimal的equals比较的是BigDecimal的value和scale,如果我们希望只比较BigDecimal的value,可以使用compareTo方法
20、不能直接使用Arrays.asList来转换基本类型数组
Arrays.asList返回的List不支持增删操作
对原始数组的修改会影响到我们获得的那个List
List list = new ArrayList(Arrays.asList(arr));
21、使用List.subList进行切片操作居然会导致OOM?
22、MySQL中sum函数没统计到任何记录时,会返回null而不是0,可以使用IFNULL函数把null转换为0;
MySQL中count字段不统计null值,COUNT(*)才是统计所有记录数量的正确方式。
MySQL中使用诸如=、<、>这样的算数比较操作符比较NULL的结果总是NULL,这种比较就显得没有任何意义,需要使用IS NULL、IS NOT NULL或 ISNULL()函数来比较。
23、捕获和处理异常容易犯的错
1)不在业务代码层面考虑异常处理,仅在框架层面粗犷捕获和处理异常
2)捕获了异常后直接生吞
3)丢弃异常的原始信息
4)抛出异常时不指定任何消息
24、捕获了异常打算处理的话,除了通过日志正确记录异常原始信息外,通常还有三种处理模式:
1)转换,即转换新的异常抛出。对于新抛出的异常,最好具有特定的分类和明确的异常消息,而不是随便抛一个无关或没有任何信息的异常,并最好通过cause关联老异常。
2)重试,即重试之前的操作。比如远程调用服务端过载超时的情况,盲目重试会让问题更严重,需要考虑当前情况是否适合重试。
3)恢复,即尝试进行降级处理,或使用默认值来替代原始数据。
25、
1)虽然try中的逻辑出现了异常,但却被finally中的异常覆盖了
2)千万别把异常定义为静态变量(把异常定义为静态变量会导致异常信息固化,这就和异常的栈一定是需要根据当前调用来动态获取相矛盾)
3)提交线程池的任务出了异常会怎么样?(从线程名的改变可以知道因为异常的抛出老线程退出了,线程池只能重新创建一个线程。如果每个异步任务都以异常结束,那么线程池可能完全起不到线程重用的作用),execute提交的任务会这样,submit提交的任务,只有在获取执行结果的时候才会抛出异常(既然是以submit方式来提交任务,那么我们应该关心任务的执行结果,否则应该以execute来提交任务)
修复方式有2步:
以execute方法提交到线程池的异步任务,最好在任务内部做好异常处理;
设置自定义的异常处理程序作为保底,比如在声明线程池时自定义线程池的未捕获异常处理程序:
26、配合使用标记和EvaluatorFilter,实现日志的按标签过滤,是一个不错的小技巧
27、使用Logback提供的AsyncAppender即可实现异步的日志记录
28、在进行文件IO处理的时候,使用合适的缓冲区可以明显提高性能
29、默认情况下,在反序列化的时候,Jackson框架只会调用无参构造方法创建对象
30、定义的static的SimpleDateFormat可能会出现线程安全问题,比较好的解决方式是,通过ThreadLocal来存放SimpleDateFormat:
private static ThreadLocal<SimpleDateFormat> threadSafeSimpleDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));