Java学习笔记
- JVM
- 常用
- JVM运行模式
- JVM的锁
- 偏向锁
- 轻量级锁
- 自旋锁
- 锁消除
- 锁粗化
- MQ
- RabbitMQ
- AMQP基本概念
- 死信队列
- RabbitMQ如何保证消息不丢
- Kafka
- ack机制
- 高性能的关键
- 限流
- 计数器
- 滑动窗口
- 漏桶
- 令牌桶
- Keytool
- Maven
- Gradle
- package into a fat jar
- 面试
- 微服务
- 设计的原则
- CAP理论
- BASE理论
- Java
- 不通过构造函数也能创建对象吗?
- 引用类型
- 单例模式
- Spring
- AOP的原理
- BeanFactory与FactoryBean
- 解决循环依赖的方式
- Spring实现单例模式
- SpringBoot自动配置的原理
- Tomcat三种模式
- undertow
- final
- 线程池
- ThreadLocal
- sleep, yield, join, wait, notify, notifyAll
- 代理模式
- CAS
- ABA问题
- IO模型
- 协程
- Kafka高吞吐揭秘
- 注解
- for循环直接删除ArrayList中的特定元素
- MyBatis原理
- Maven的生命周期
- 普通内部类与静态内部类
- Java类的初始化顺序
- ArrayList和LinkedList的区别
- HashMap和HashTable的区别
- LinkedHashMap
- TreeMap实现一致性哈希
- CopyOnWriteArrayList
- 对象占用空间
- 为什么要有hashCode
- 在以下4种特殊情况下,finally块不会被执行:
- synchronized
- == 和 equals有什么区别
- Synchronized和ReenTrantLock对比
- AQS
- ReadWriteLock
- Snippets
- 收藏
JVM
常用
- 关闭大部分类验证措施,缩短类加载时间
-Xverify:none
- 最大堆大小
-Xmx
- 初始堆大小
-Xms
- 年轻代大小
-Xmn
JVM运行模式
- 当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,,服务起来之后,性能更高
-Xmixed为混合模式(默认),-Xint为解释模式,解释模式速度慢10倍
JVM的锁
偏向锁
如果第一个线程获取到了锁,在执行过程中,没有其他线程竞争,那持有锁的线程就不需要同步
轻量级锁
如果有竞争,偏向锁升级为轻量级锁,轻量级锁使用的是CAS操作
自旋锁
轻量级锁失效后就升级为自旋锁,未获取到锁的线程不用去到操作系统内核态进行挂起,而是用忙循环来等待,有一个默认的等待次数。如果等待的次数由前一次自旋的时间和锁的拥有者的状态决定,就叫自适合自旋锁
锁消除
JVM发现某些加了锁的地方不可能存在竞争,就直接把这个锁去掉
锁粗化
如果对同一个对象多个函数进行加锁,会频繁对同一个对象上锁解锁,影响性能,所以就应该将锁粗化
MQ
RabbitMQ
producer --> broker customer --> exchange --> queue --> consumer
- broker发送给exchange成功之后,会返回confirmCallback
- exchange发送给queue成功之后,会返回returnCallback
- consumer可以做ack
- 事务机制,可以消费了多条消息之后,再提交或回滚事务
AMQP基本概念
- Broker: 控制所有动作的一个协调者
- exchange: 交换机,决定生产者发送的消息发到哪个队列
- binding: exchange的策略
- queue
- routing key
- vhost: 一个broker可以有多个virtual host
- channel: 一个连接里面的虚拟连接
死信队列
- DLX: Dead letter exchange, 死信交换机,那些过期的,超过队列长度的或者被拒绝的消息会放到这里。DLX绑定的队列称为死信队列
- 可以通过死信队列实现延迟队列。比如订单30分钟之后未支付,需要自动取消。普通做法是通过定时任务。现在的做法是,给一个普通exchange发消息,它绑定了死信队列,设定30分钟过期。30分钟之后,rabbitmq会自动将该消息转到死信队列里面,绑定特定的消费者对订单进行取消处理。处理时先判断订单是否支付了,如果已支付就跳过,否则就取消该订单,恢复库存等。
RabbitMQ如何保证消息不丢
- 持久化,Exchange, Queue, Message都设置持久化
- 要ack
- 部署时要将集群设置成镜像模式
- 消息补偿机制,消息进db,比如hbase
- 使用事务功能,使用事务,会降低吞吐量
Kafka
- Kafka在消费端是使用pull的方式消费消息的
ack机制
- ack = 0, 只保证producer发送了消息,不管它是否成功
- ack = 1, 写入主分区就认为成功了。这是默认的设置
- ack = -1, 需要写入所有分区才认为成功
高性能的关键
- 顺序读写
- 零拷贝
限流
计数器
利用Redis的Incr,设置过期时间,如果get出来的数大于限额,则拒绝,否则incr. 缺点:在两个时间区间过渡的小区间,会出来超限额的情况,即请求分布不均匀。
滑动窗口
将20s内100个请求的限额分成4个小窗口,则每个小窗口是5s, 不能超过25个请求。设窗口大小为2,则每次统计窗口内的请求数,不能超过50个。 分的小窗口越多,越平滑。
漏桶
固定速度消费,设置一个容器,定时以恒定速率remove元素,而生产元素的速率不变。 如果容器本来没怎么满,这个算法就可以起到一定的削峰平滑作用。 缺点:如果容器本来也快要满了,短时间内涌入太多请求,则会拒绝大部分请求。
令牌桶
固定速度生产令牌,取令牌的速率不固定。 如果容器本来就快要满了,这个算法就可以起到一定的削峰平滑作用。 缺点:如果容器本来也快要空了,短时间内涌入太多请求,获取不到令牌,则会拒绝大部分请求。 这个算法的最大好处是,当发现容器快要空的时候,可以加快令牌的发放速率,可是这样不就压垮了服务吗?
Keytool
- Check cert
keytool -list -v -keystore aaa.jks
- generate keystore file
keytool -genkey -keyalg RSA -alias selfsigned -keystore aaa.jks -storepass password -validity 360 -keysize 2048
- export public key
keytool -export -keystore aaa.jks -alias signFiles -file aaa.cer
- p12 to crt
openssl pkcs12 -in keystore.p12 -nokeys -out aaa.crt
Maven
- offline build
mvn clean package -o
- download source
mvn dependency:source
- skip test
-Dmaven.test.skip=true
- upload source
mvn source:jar install
- force download
mvn dependency:resolve -U
Gradle
package into a fat jar
jar {
manifest {
attributes "Main-Class": "io.github.zhengyhn.pan.PanClientCommand"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
面试
微服务
设计的原则
- 单一职责
- 服务自治
- 轻量级通信
- 粒度进化:随着业务发展而发展
CAP理论
C: consistence, 一致性
A: availability, 可用性
P: Partition torelance, 分区容错性 三者只能同时满足两者。
CA: 这时P不满足,则退化为了单机系统,不是分布式系统,单机系统当然同时满足一致性和可用性
CP: 要同时满足一致性和分区容错性,则需要一定的时间进行主从同步,同步期间从节点不可用,这时不满足A
AP: 要同时满足可用性和分区容错性,则不能保证任何时刻两个节点查到的数据是一致的,因为需要保证任何时刻都可用,这时只能实现最终一致性,不满足C
BASE理论
这是针对同时满足AP的情况下的一个理论。由eBay提出。
- Basical available: 保证基本可用
- Soft state: 允许其中出现软状态
- Eventually consistent: 最终一致性
Java
- 打印默认JVM参数
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.242-b08, mixed mode)
其中UseCompressedClassPointers表示,虽然JVM是64位的,但是使用压缩的类指针,还是4个字节。而UseCompressedOops表示,普通ordinary object pointer也是使用4个字节。
- JTA: Java transaction API
- JTS: Java transaction service,
- 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
- @Component 注解作用于类,而@Bean注解作用于方法
- Java 对象的创建过程
- Step1:类加载检查
- Step2:分配内存
- Step3:初始化零值
- Step4:设置对象头
- Step5:执行 init 方法
- 对象的访问定位
- 句柄池(两次指针定位)
- 直接指针
- 回收算法
- 标记清除
- 标记复制(新生代)
- 标记整理(老生代)
- 回收器
- Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择
- ParNew是许多运行在 Server 模式下的虚拟机的首要选择
- Parallel Scavenge , jdk1.8默认
- CMS, 并发标记并发清除
- G1, 主要利用多核
- ZGC, jdk11
- 分库
- 你将数据库中的用户表和用户订单表分别放在两个不同的数据库
- 由于用户表数据量太大,你对用户表进行了水平切分,然后将切分后的 2 张用户表分别放在两个不同的数据库
- 分表
- 垂直拆分是对数据表列的拆分,把一张列比较多的表拆分为多张表
- 水平拆分是对数据表行的拆分,把一张行比较多的表拆分为多张表
- ShardingSphere
- AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升
- 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
- -XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小) -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
- Runnable 接口 不会返回结果或抛出检查异常,但是 Callable 接口 可以
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
- submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
- 两者最主要的区别在于:sleep() 方法没有释放锁,而 wait() 方法释放了锁
- CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法
- 比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
- transient 只能修饰变量,不能修饰类和方法。
- transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
- 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
- checked exception: IOException, SQLException, ClassNotFoundException
- 在以下 3 种特殊情况下,finally 块不会被执行:
- 在 try 或 finally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
- 程序所在的线程死亡。
- 关闭 CPU。
不通过构造函数也能创建对象吗?
- 用 new 语句创建对象,这是最常见的创建对象的方法。 (2) 运用反射手段,调用 java.lang.Class 或者 java.l ang.reflect.Constructor 类的 newInstance() 实例方法。 (3) 调用对象的 clone() 方法。 (4) 运用反序列化手 段,调用 java.io.ObjectInputStream 对象的 readObject() 方法。 (1)和(2)都会明确的显式的调用构造函数 ;(3)是在内存上对已有对象的影印,所以不会调用构造函数 ;(4)是从 文件中还原类的对象,也不会调用构造函数。
引用类型
- 强引用
- 软引用:在JVM内存不足时,就会回收软引用
- 弱引用:在GC时只要扫描到,就回收
- 虚引用:为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,一般用于管理堆外内存,需要通知GC来回收堆外内存。
单例模式
- 内部静态类实现的单例模式,利用了一个特性:内部静态类,只有第一次使用的时候才会被加载,所以是属于懒汉式
- 另外一个特性是,new的对象保存在内部静态类的静态成员中,只有在类加载的时候才被创建,由JVM保证线程安全
- 这样,即延迟加载,又线程安全
Spring
AOP的原理
- 在容器创建对象时,会判断该类是否被代理,如果是,则通过动态代理创建代理类,返回代理类而不是原始类。
BeanFactory与FactoryBean
- BeanFactory是古老的容器,ApplicationContext extends ListableBeanFactory extends BeanFactory. 用它来定制对象,实现比较复杂
- FactoryBean, 是一个用来简化定制对象流程的接口,创建的bean一样可以交给容器管理
解决循环依赖的方式
核心要点:通过增加中间态。new --> 初始化 --> 填充属性之后实例化 如果是构造器注入,则无法解决循环依赖问题,因为构造器注入要求注入的对象必须是实例化之后的。 三级缓存:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
// 从上至下 分表代表这“三级缓存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
...
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的 都会放进这里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}
- 先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
- 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
- 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)
Spring实现单例模式
- 使用的是单例注册表的方式,可以继承
- 在类的静态初始块new对象,在类加载的时候执行,放进HashMap中,key为类名
SpringBoot自动配置的原理
- 约定大于配置,starter默认做了一些配置,你要改,就在配置文件覆盖
Tomcat三种模式
- BIO,8.0以下版本使用
- NIO,8.0及以上版本默认
- APR, ApachePortableRuntime, linux要安装这个依赖,可以使用JNI的方式来处理IO,大大提高性能
undertow
- 轻量级:它是一个 Web 服务器,但不像传统的 Web 服务器有容器概念,它由两个核心 Jar 包组成,加载一个 Web 应用可以小于 10MB 内存
- Servlet3.1 支持:它提供了对 Servlet3.1 的支持
- WebSocket 支持:对 Web Socket 完全支持,用以满足 Web 应用巨大数量的客户端
- 嵌套性:它不需要容器,只需通过 API 即可快速搭建 Web 服务
final
- 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法
- final方法不能被重写,比非final方法要快,因为静态绑定,运行时不会动态绑定
线程池
- corePoolSize, 核心线程数,一直存活的线程数, 等于每秒需要多少个线程处理
- allowCoreThreadTimeout,是否允许核心线程超时,默认为false,如果设置为true,核心线程没事干的时候就会退出
- queueCapacity,阻塞队列容量,默认是整数最大值,所以这个值一定要设置,
- maxPoolSize, 最大线程数
- keepAliveTime, 当线程空闲时间达到这个的时候,就会退出
- 线程池使用了生产者消费者模式,就是那个阻塞队列的实现
ThreadLocal
- 使用ThreadLocalMap来存放,这个Map的Entry对象中使用WeakReference来保存ThreadLocal,防止出现内存泄露
- Entry继承WeakReference类,key为ThreadLocal对象,当不需要时,Key为弱引用,可以让GC回收Key值
- 还有内存泄露问题,就是Value没回收,所以需要每次调用remove方法
sleep, yield, join, wait, notify, notifyAll
- sleep: 让线程休眠进入阻塞状态,并释放cpu,让其他任意优先级的线程运行,如果有同步锁,不释放锁
- yield: 让出cpu,但是不指定多久,只能让相同优先级或更高优先级的线程执行,进入可执行状态,所以有可能立马又调度到自己
- wait, notify, notifyAll是Object类的方法,配合锁机制使用的,必须在synchronized块中使用,调用了wait之后,当前线程就会释放锁标志,放入对象等待池中。调用notify之后,会从对象等待池中移动任意一个线程到锁标志等待池,调用notifyAll就是移动所有的到锁标志等待池。
- sleep, yield是静态方法,是因为对象跟线程是不一样的,同样一个对象,可以开多个线程,让一个线程sleep,就应该以线程为单位,而不是对象。
代理模式
- 静态代理
- 动态代理,需要接口
- cglib动态代理,直接代理类
CAS
- compare and swap,在进行操作时,传入内存地址,预期原始值,新值,当从传入内存地址中读取的值为预期原始值时,就替换成新值,否则什么都不做。
ABA问题
- 在检查内存中的值是为a, 准备给它赋值时又是a,并不代表它没有被修改过。因为这段时间,a可能被修改成了b,又修改回了a
IO模型
每次IO,都要经历两步
将数据从磁盘文件加载至内核空间(缓冲区),等待数据完成,时间较长
将数据从内核空间的缓冲区复制到用户空间的进程内存中,时间较短 linux的5种IO模型
阻塞IO(blocking IO): 线程IO时一直等到数据从文件读到内核缓冲区,然后从内核空间拷贝到用户空间
非阻塞IO(nonblocking IO): 线程IO时,请求读文件后,会继续往下执行做其他事情,然后轮询检查刚刚读的文件是否完成,完成后,从内核空间拷贝到用户空间,IO完成
IO复用(IO multiplexing): 也叫事件驱动模型,每个IO请求在内核注册fd(文件描述符),然后复用一个线程去处理不同的IO请求,当其中有一个数据读取到内核缓冲区之后,就可以复制到用户空间,一个一个处理。
信号驱动IO(signal driven IO):应用进程先向内核注册一个信号处理函数,不阻塞,应用进程可以做其他事情,当内核读取文件内容到缓冲区之后,内核就会发一个信号给用户进程,用户进程把数据拷贝到用户空间。
异步IO(asynchronous IO): 应用进程把IO请求传给内核后,完全由内核去操作,内核把数据拷贝到用户空间,最后发信号通知用户进程IO完成。 前4种是同步的,最后一种是异步的。 同步是指内核空间拷贝数据到用户空间的过程是不是同步的。 IO复用模型的几种方式:
select: 一个线程,去拿到所有准备好数据的fd,然后遍历每个fd,拷贝数据到用户空间,完成IO。select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制。每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍
poll: 跟select很像,但是没有文件描述符的最大限制,因为是用链表实现,select应该是固定数组,一样poll的过程中需要遍历整个链表,找到就绪的fd
epoll: select和poll是调用了这2个系统调用之后,才去遍历扫描,而epoll是事先通过epoll_ctl注册文件描述符,当某个文件描述符就绪之后,会有回调的机制,激活它,当调用epoll_wait时得到回调,不用遍历。
协程
是一种协作式的用户态线程。以前,线程和进程都是抢占式执行的,是操作系统自动帮我们切换,让我们感觉是同步进行的。协程是用户进程手动切换,省资源,速度快。
Kafka高吞吐揭秘
- 利用文件的顺序写,将数据追加到文件结尾,比随机定位然后写要快
- 使用PageCache,是内核空间的一个内存缓冲区,把数据写到这里
- 使用sendFile,从磁盘读取到缓冲区之后,不像传统的拷贝到用户空间再拷贝到内核的Socket缓冲区,而是直接在内核中拷贝到Socket缓冲区
注解
有这么三种:
- Java自带注解,一般用于编译阶段检查,比如@Override
- 元注解,用于定义注解的注解,@Documented是标明是否文档
- 自定义注解,一般是运行阶段使用反射注入到对象中 类文件中会包含一个RuntimeVisibleAnnotations属性,在类的字节码里面。 其实注解被编译后的本质就是一个继承Annotation接口的接口,当我们通过AnnotationTest.class.getAnnotation(Test.class)调用时,JDK会通过动态代理生成一个实现了Test接口的对象,并把将RuntimeVisibleAnnotations属性值设置进此对象中,此对象即为Test注解对象,通过它的value()方法就可以获取到注解值
for循环直接删除ArrayList中的特定元素
- 删除元素的源码是删掉,然后移到后面的元素在前面,所以下一个元素会定位不到
- 可以用倒序删除的方法来避免
- 也可以用iterator来解决
MyBatis原理
- SqlSessionFactory,一个数据库一个对象
- SqlSession,一次请求会new一个,处理完之后,关闭
- DefaultSqlSession不是线程安全的
- Mapper的作用是发送sql,并拿到结果
- 默认情况下只开启一级缓存,在同一个sqlSession,相同的sql语句调用一个mapper方法,只执行一次sql语句
- 二级缓存需要自己配置,二级缓存失效时会清除整个namespace的缓存,浪费
Maven的生命周期
- 共有3个生命周期: clean(清理文件), default, site(生成文档等)
- default有这些阶段,validate, compile, test, package, verify, install, deploy
普通内部类与静态内部类
- 普通内部类跟包含它的类有引用,可以使用包含它的类的属性和方法
- 静态内部类跟包含它的类没有关系,只是借个地方
- 普通内部类不能定义静态变量和方法,因为InnerClass相当于OuterClass的一个非静态成员,必须要等到OuterClass的对象初始化之后才初始化InnerClass,而静态变量和方法是在对象创建之前加载的,这矛盾了。
- fastjson使用内部类要用静态内部类
Java类的初始化顺序
基类静态变量和基类静态代码块(按代码的先后位置) --> 派生类静态变量和基类静态代码块(按代码的先后位置) --> 基类初始化块和成员变量 --> 基类构造函数 --> 派生类初始化块和成员变量 --> 派生类构造函数
ArrayList和LinkedList的区别
- 两者都是线程不安全的
- ArrayList底层是数组,LinkedList是双向链表
- ArrayList插入和删除时间复杂度为O(n),LinkedList是O(1)
- ArrayList访问的时间复杂度为O(1),LinkedList为O(n)
- ArrayList由于是变长数组,所以会有一些空闲空间。LinkedList虽然没有空闲空间,但是有很多指针,占用更多空间
HashMap和HashTable的区别
- HashTable线程安全,所以性能要低一些
- HashMap可以用null做为key,HashTable会报错
- Java8的HashMap做了扩容时的优化,扩容为原来的2倍,新索引要么在原位置,要么是原索引加原长度,所以不需要重新计算hash值,只需要判断原来计算的hash在左边新的位是0还是1,如果是0,不变,否则为原索引加原来的长度
- hashmap 的数组长度为什么要保证是 2 的幂,是为了用hash & (length - 1)代替hash % length,因为位运算效率更高,但是这个要求length必须是2的幂
LinkedHashMap
- 继承于HashMap,重写了部分方法
- 节点加了2个字段,before和after,用于维护双向链表
- 每次put的时候,就加到双向链表的结尾
- 构造函数时,可以开启查询排序功能的开关,这样每次put或get的时候都会把这个节点移到到链表结尾
- LRU缓存就是这样实现的
TreeMap实现一致性哈希
- 为了满足单调性,新增节点和移除节点只影响较少的value重新hash,采用一种环的思想,将所有节点映射到32位的key值,顺时针找离它最近的节点
- 为了满足平衡性,当移除节点时需要转移全部value到下一个节点,下一个节点又不够空间放,又到下一个,造成雪崩,设计了虚拟节点机制,比如虚拟20个节点对应1个真实节点
- TreeMap在这里的应用是,hash得到key值后,去找比key值大的节点,找到第一个,这里就利用了TreeMap的二分搜索特性,可以在O(logN)的时间复杂度内找到
CopyOnWriteArrayList
- 普通的ArrayList,在遍历的过程中,如果另外一个线程对它进行增删元素,是会出问题的
- copy on write,就是在写入的时候,先上ReentrantLock锁住,然后复制原来的内容到一个新数组,在新数组写入后,再将引用指向新数组,最后解锁
- 这样在遍历的时候就不怕被其他线程增删元素了,因为遍历的时候读的是旧的数据
- 适合读多写少,集合不大,实时性要求不高的情况下,如白名单什么的
对象占用空间
Object ob = new Object();
这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是栈中保存引用的所需要的空间,而那8byte则是Java堆中对象的信息。
为什么要有hashCode
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
在以下4种特殊情况下,finally块不会被执行:
在finally语句块中发生了异常。 在前面的代码中用了System.exit()退出程序。 程序所在的线程死亡。 关闭CPU。
synchronized
是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
== 和 equals有什么区别
== 是一个运算符,如果有一边是基本类型,则比较的是值。如果两边都是引用类型,则比较的是地址。 equals是对象的一个方法,Object类默认的实现是比较两者的地址,一般都会重写这个方法,具体以重写的逻辑为准。 像Integer重写的方法,如果传进来的是基本类型,会进行自动装箱,然后比较类型,再比较值。
Synchronized和ReenTrantLock对比
- 都是可重入锁,拿了之后再拿,计数器加1,
- Synchronized是JVM层面的,不用手动加锁解锁,ReenTrantLock是API层面的,需要加锁解锁try catch
- ReenTrantLock更灵活,功能更多
- 可以中断等待,正在等待的线程通过lock.lockInterruptibly()可以放弃等待,去做其他事情
- Synchronized只能是非公平锁(JVM帮你做的),ReenTrantLock可以指定公平锁还是非公平锁
- ReenTrantLock可以利用Condition类实现选择性通知
AQS
是一个抽象类AbstractQueuedSynchronized,定义了一些同步的基础组件,比如CAS,compareAndSet,tryAcquire, tryRelease等,ReenTrantLock的实现就是聚合了AQS的一个子类
ReadWriteLock
ReentrantReadWriteLock维护了2个锁,一个是读锁(共享锁),一个是写锁(排他锁),只要有写的时候就互斥,不能读也不能写。
Snippets
- 初始化数组为某个值
import java.util.Arrays;
int[] first = new int[26];
Arrays.fill(first, 0);
收藏
https://www.processon.com/view/link/5c4ea49ae4b08a7683b74d72
springboot知识图谱 思维导图
https://www.processon.com/view/link/5c6b68b5e4b0641c83fa3708
rocketmq-core 思维导图
https://www.processon.com/view/link/5c6fb06be4b07fada4efec49
并发知识汇总 思维导图
https://www.processon.com/view/link/5c6fad91e4b0f9fba68e7b25
redis基础知识汇总 思维导图。
https://www.processon.com/view/link/5c6fae7fe4b0c4e2165d80c1
机器学习实战笔记 思维导图
https://www.processon.com/view/link/5c6faec5e4b08a7683cb3374
Python学习笔记 思维导图
https://www.processon.com/view/link/5c6faf2fe4b08a7683cb34b7
spring boot admin 思维导图
https://www.processon.com/view/link/5c6faf4ae4b0f9fba68e8060
spring boot actuator 思维导图