赞
踩
✨ 牛客刷题日记–理解为重中之重
教就是最好的学,即使咱们程序员是代码说话,但是当你能够清楚地表述出你的技术点,并且让别人也能够明白,那你一定是技术过硬并且是行业大咖了解题思路 得分点 地址空间、开销、并发性、内存 标准回答 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
极简回答:
引申一点-
牛客很牛 问题是线程和协程的区别,解题思路是进程和线程的区别那么个人就浅浅分析一下协程重点:区分并发编程的进程、线程、协程、纤程、管程之间区别
关于协程
- 是一种用户态的轻量级线程,协程的调度完全由用户控制,操作系统内核对协程一无所知;
- 协程的调度完全由应用程序来控制,操作系统不管这部分的调度;
- 协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
- 一个线程可以包含一个或多个协程;
- 协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下纹和栈保存起来,在切换回来时恢复先前保运的寄存上下文和栈;
- 协程能保留上一次调用时的状态,看到这里各种生成器(生成器是被阉割的协程)的概念浮现出来了。
口述简答:索引虽然会提高查询的效率,但会降低更新表的效率
举个简单的例子,索引就好比是一本书的目录,如果给书本排好了目录,那么只需要根据书本的目录,就可以定位到某个知识点的页码,而不用逐页逐页地查找,极大的节省查找书本内容的时间。
索引的好处: 1.能够提高数据的查询性能,降低数据库的IO成本。 2.能够提高数据分组、数据排序的性能。
索引的缺点: 1.索引需要占用一定的存储空间(创建和维护索引需要耗费时间和空间,会增加存储压力)2.对数据表的数据进行增、删、改、查时,MySQL内部需要对索引进行动态维护;如果是频繁进行修改的表,维护索引需要耗费更多的时间空间
个人理解毫无坏处哈哈哈,索引就是好用!
一个线程是程序中一个单一的控制流程,多线程就是单个程序中运行多个线程执行不同的任务,达到提高效率提高资源的利用率目的。
缺点:线程之间共享进程的资源,会容易造成死锁。对多个线程的管理会带来额外的CPU开销,同时系统上下文切换也会带来额外的负担。线程的终止会对程序造成影响。
- 线程是程序执行的最小单元,一个线程执行一个任务;各个线程共享程序的内存空间
- 一个进程可以拥有多个线程,各个线程之前共享进程的内存空间,有单独的栈内存,计数器,局部变量。
- 多线程优点:减少程序响应时间;提高cpu利用率;创建和切换开销小,数据共享效率高;简化程序结构
- 实现多线程有三种方式:继承Thread类,实现Runable接口,实现Callable接口
从理论上来说保证线程安全需要保证java内存模型的三大特性:原子性,可见性,有序性。从实际方法来说可以使用java.util.concurrent提供的原子类使用乐观的CAS方式来实现线程安全,使用jvm提供的synchronize这种悲观的处理方式来实现线程安全,使用jdk提供的reentrantLock,或是在某些特殊条件下使用volatile来保证数据的可见性和有序性来实现线程安全,但是volatile并不能一定保证线程安全。
通过synchronized关键字可以在单机情况下保证线程安全。
分布式情况下可以通过乐观锁、悲观锁以及分布式锁进行实现,保证线程安全。
保证线程安全的有三种方式:原子类、volatile、锁。
- 原子类:遵循CAS即“比较和替换”规则,比较要更新的值是否等于期望值,如果是则更新,如果不是则失败(单共享变量)
- volatile关键字:轻量级的synchronized,在多处理器开发中保证了共享变量的“可见性”,从而可以保证单个变量读写时的线程安全(单共享变量)
- synchronized,java中常用的锁有两种:synchronized+juc包下的lock锁。支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量
2023.04.03更新以下
解题思路 得分点 争夺共享资源、相互等待、互斥条件、请求和保持条件、不剥夺条件、环路等待条件
标准回答
- 死锁:两个或者两个以上的线程互相争夺对方的资源而不释放自己的资源,从而导致死锁的产生。
- 死锁产生的条件:
- 互斥:一个资源在同一个时刻只能由一个线程执行
- 请求与保持:一个线程在请求被占用资源时,对已经获得的资源保持不放。
- 循环等待:发生死锁时所有的线程都会形成一个死循环,一直阻塞。
- 不可剥夺条件:线程对所获得的资源在未使用完时不能被其他线程剥夺,只能自己释放。 避免死锁的方法就是破坏死锁产生的条件。
1.管道:管道本质是内核中维护的一块内存缓冲区。
2.命名管道:因为无名管道只适用于具有亲缘关系的线程,所以就衍生出来命名管道,这样就可以实现了非亲缘关系进程之间的通信。
3.信号:一种通知机制。
4.消息队列:是一个消息链表,既可以读消息,也可以写消息。
5.共享内存:多个线程共享一片内存区域。
6.内存映射:就是将磁盘文件数据映射到内存,通过修改内存就能修改磁盘文件。
7socket接口,socket接口一般用于不同主机上进程之间的通信
2023.04.06更新
- Redis的数据结构分为5种,字符串(String)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)
- 字符串类型(String),其底层是一个简单的动态字符串,可以存储字符串,数字和二进制数据。
- 哈希(hash)底层结构为ziplist和hashtable两种,其存储的是键值对。
- 列表(list),存储线性有序且可重复的数据,底层结构有ziplist和linkedList,quicklist三种。
- 集合(set)无序不可重复,底层结构分为inset和HashTable两种。
- 有序集合(zset),有序不可重复,通过给每个元素设置一个分数来作为排序的依据,底层结构为ziplist和skiplist
- 乐观锁总是假设最好的情况,就是每次去拿数据的时候都认为别人不会修改,但在更新的时候会判断别人有没有去更新这个数据,可以通过版本号机制和CAS算法实现、乐观锁适用于多读的应用类型,这样可以提高吞吐量,例如redis。
- 悲观锁总是假设最坏的情况,就是认为每次去拿数据的时候都会认为别人会修改数据,所以每次拿数据的时候都会上锁,常使用在传统关系型数据库中。
- mysql数据库的共享锁和排他锁都是悲观锁的实现。
单例模式、工厂模式
- 常用的设计模式有单例模式、工厂模式、代理模式、适配器模式、装饰器模式、模板方法模式等等。
- 像sping中的定义的bean默认为单例模式,spring中的BeanFactory用来创建对象的实例,他是工厂模式的体现。
- AOP面向切面编程时代理模式的体现,它的底层就是基于动态代理实现的。适配器模式在springMVC中有体现,它的处理器适配器会根据处理器规则适配相应的处理器执行,模板方法模式用来解决代码重复的问题等
AOP(aop: aspect oriented programming) 有切面,连接点,通知,切点。是spring两大核心之一,是一种编程思想,是对OOP的一种补充。它可以对业务逻辑的各个部分进行隔离,降低耦合,提高代码的可重用性。它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。(在方法上开一个切口,可以在这个类执行之前执行自定义的方法,在不修改源代码的前提下,对方法进行增强)
- aop是基于动态代理的技术,在程序运行的时候,动态的修改class字节码生成新的class文件进行运行的技术,可以对某个类的某个方法、多个类的多个方法,通过配置切点表达式进行匹配,然后改造被匹配到的类的某一些方法,然后运行;
- 动态代理已知两种
- cjlib的基于子类的动态代理(可以在运行期间创建子类的动态实例),不能对final修饰的类进行代理
- jdk自带的基于接口的动态代理(可以在运行时创建接口的代理实例),SringAOP只能对IOC容器中的Bean进行增强,对于不受容器管理的对象不能增强
- 常见的aop使用场景,
日志统一管理,事务管理器。
2023.04.09更新
- reids的持久化方式有两种:RDB和AOF
- RDB持久化:将当前进程中的数据已生成快照的方式保存到硬盘中,是redis默认的持久化机制。(指定间隔内将数据集快照写进磁盘中的dump.rdb)
- 优点:持久化时生成的文件体积小,恢复数据快
- 缺点:每次运行都需要执行fork操作,RDB持久化策略,没有做到实时的持久化,有时可能会丢失最后一步的数据。
- RDB持久化:以独立日志的方式记录每次写入的命令,重启时执行AOF中的命令即可恢复数据。(Append only file以日志形式记录每个写操作,只允许追加不允许改写)
- 优点:AOF持久化的安全性更高,保证了数据持久化的实时性。
- 缺点:文件要大很多,恢复速度慢。
- RDB-AOF持久化:这种方式是基于AOF持久化方式构建出来的。兼具RDB和AOF的优势。
- 优先级AOF>RDB
- Redis默认是用RDB文件存储
解题思路 得分点 饿汉式单例模式、懒汉式单例模式、线程安全的懒汉式单例模式
标准回答 单例模式(Singleton Pattern)是最简单的创建型设计模式。它会确保一个类只有一个实例存在。单例模式最重要的特点就是构造函数私有,从而避免外界直接使用构造函数直接实例化该类的对象。 单例模式在Java种通常有两种表现形式:
饿汉式:类加载时就进行对象实例化
懒汉式:第一次引用类时才进行对象实例化 饿汉式单例模式: 在类被加载时就会初始化静态变量instance,这时候类的私有构造函数就会被调用,创建唯一的实例。
public class Singleton{
private static Singleton instance = new Singleton(); // 构造方法私有,确保外界不能直接实例化
private Singleton(){ } //通过公有的静态方法获取对象实例
public static Singleton getInstance(){
return instance;
}
}
懒汉式单例模式: 类在加载时不会初始化静态变量instance,而是在第一次被调用时将自己初始化
public class Singleton {
private static Singleton instance = null; // 私有构造方法,确保外界不能直接实例化。
private Singleton() {} // 通过公有的静态方法获取对象实例
public static Singleton getInstace() {
if (instance == null) {
instance = new Singleton();
} return instance;
}
}
但这时有一个问题,如果线程A和B同时调用此方法,会出现执行if (instance == null)语句时都为真的情况,那么线程AB都会创建一个对象,那内存中就会出现两个对象,这违反了单例模式的定义。为解决这一问题,可以使用synchronized关键字对静态方法 getInstance()进行同步,线程安全的的懒汉式单例模式代码如下:
public class Singleton {
private static Singleton instance = null; // 私有构造方法,确保外界不能直接实例化。
private Singleton() {} // 通过公有的静态方法获取对象实例
synchronized public static Singleton getInstace() {
if (instance == null) {
instance = new Singleton();
} return instance;
}
}
饿汉式单例类在资源利用效率上不如懒汉式单例类,但从速度和反应时间来看,饿汉式单例类要优于懒汉式单例类。
加分回答 单例模式的优点:
单例模式分为饿汉模式,懒汉模式,饿汉模式是实例提前创建好放到内存中等待使用,懒汉模式是在第一次对象调用方法执行时创建实例,线程安全的。单例模式是指一个对象只能实例化一次,节省内存,但是会降低性能,优点是节省资源,降低创建,销毁性能消耗。缺点是不能扩展,不能创建子类,对测试不利,并行开发环境中单例类没有完成不能测试
单利模式
public class SingleTon { private static single= null; public Single(){ } public syncronized static Single getSingle(){ if( single = null){ single = new single(); } } }
理内存:计算机中真实拥有的内存。物理内存是有限的,容易产生内存不足问题。虚拟内存是一种抽象的逻辑概念,拥有连续的内存地址。
2023.04.13更新
Ioc是spring的基本特性之一,意思是控制反转,将原本由人完成的类的实例化等操作交给容器去完成,可以降低对象之间的耦合度,实现方式为di:依赖注入
- IoC:控制反转,一种面向对象的编程思想。是Spring框架种的一个核心理论思想,主张将对象的创建、属性赋值、生命周期等操作的控制交由spring容器进行管理,降低代码之间的耦合性。
- 控制:对象的创建的控制权限;
- 反转:将对象的控制权限交给spring。
- 之前我们创建对象时用new,现在直接从spring容器中取,维护对象之间的依赖关系,降低对象之间的耦合度。
- 实现方式为DI,依赖注入,有三种注入方式:构造器、setter、接口注入
- 接口注入已经被淘汰了
- 属性setter注入方式,Spring通过无参构造或无参静态工厂方法实例化Bean对象后,再通过调用该Bean的setter方法实现注入。
- 构造器注入,Spring直接通过调用有参的构造方法实现依赖注入,每一个参数就是一个依赖。
- @AutoWired是Spring的注解只能在Spring中使用,默认采用类型匹配的方式,可以通过配合@Qualifier实现名称匹配。@Resource是JDK自带的注解,Java标准,适用于大部分框架,默认是名称匹配,匹配不到了进行类型匹配,还匹配不到就抛出异常
- 内存管理有:页式存储,段式存储,段页式存储。
- 页式存储,提高内存的利用率,有效减少内存碎片
- 段式存储,更好的反应程序的逻辑结构,有利于段之间的共享 (反映程序的逻辑结构并有利于段的共享)
- 段页式存储,结合前面两种存储方式,将程序划分为若干段
linux就是采用这种方式(段页式存储管理先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。并利用段表和页表进行从用户地址空间到物理内存空间的映射。)
- IO多路复用是指单个线程或进程能够处理多个IO请求。
- select调用主要统计有多少个文件描述符需要进行IO操作。缺点:内存开销大,支持的的文件描述符的个数有限。
- poll和select调用差别不大,主要是底层数据结构变成了链表,支持的文件描述符的个数无上限。epoll调用是更加高效的方式,底层数据结构使用了红黑树和链表,避免了大量的内存分配和轮询
2023.02.19
- 线程是创建、切换、停止都是要调用操作系统来实现的,协程则可以通过编程语言实现
- 进程、线程是同步的,协程是异步的
- 线程是抢占式,协程是非抢占式,需要用户主动释放cpu
- 线程在多核状态能实现并行,而协程是为了用来支持并发的
解题思路 得分点 未提交读、已提交读、可重复读、可串行化 标
准回答 SQL 标准定义了四种隔离级别,这四种隔离级别分别是:
事务隔离是为了解决脏读、不可重复读、幻读问题,下表展示了 4 种隔离级别对这三个问题的解决程度:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
上述4种隔离级别MySQL都支持,并且InnoDB存储引擎默认的支持隔离级别是REPEATABLE READ,但是与标准SQL不同的是,InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用Next-Key Lock的锁算法,因此避免了幻读的产生。
所以,InnoDB存储引擎在默认的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别;
加分回答
- 读未提交:读取了没有提交的一个数据,容易会产生脏读,幻读以及不可重复读
- 读已提交:读取了已提交的数据,避免了脏读的产生,但是仍出现不可重复读和幻读
- 可重复读:会产生幻读。MySQLinnodb中最高的隔离级别就是可重复读。可用MV CC并发版本控制来避免可重复读
- 串行化:最高的隔离级别,他要求的就是按照顺序一个一个来
事务就是说程序中一系列操作要么全都成功,要么全都失败,不能出现一个事务里,一些成功一些失败的情况。
- 读未提交(read uncommited):一个事务可以读取到其他事务修改了但是还没有提交的数据,所以在该隔离级别下是不加任何共享锁或者排他锁的,相应的它的执行速度最快,但是会导致脏读,不可重复读和幻读的情况发生。
- 串型化(seriazeable):当一个事务读取某个数据时,他会对整张表加共享锁,修改时,整张表加排他的,所以它的性能时最低的,但是脏读,幻读,不可重复读的问题不会出现。
- 读已提交(readcommitted):指一个事务只能读到其他事务修改提交后的数据,若数据只是修改了还未提交,那么此时读取的数据便不是当前读而是有MVCC中readview提供的数据快照(老版本数据),可以避免脏读,不可避免不可重复读和幻读。
- 可重复读(repeatable):是指一个事务连续两次读取到的数据都是相同的,无论这两次读取的过程中是否有其他事修改了该数据。他避免了脏读,不可重复读。不能避免幻读,同时它也是mysql默认的隔离级别。
- 在MVCC中每一个事务都会有一个readview,在readcommited隔离级别中:一个事务每快照读一次都会重新生成一次readview。所以他无法避免不可重复度,而repeatable中,仅在事务开启时生成一次readview,后续的读取操作都会复用这个readview所以每次读取同一个数据时读取的值都是一样的,这就是它实现避免不可重复读的原理。
- 反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。
- 获取Class对象的三种方式:
getClass();xx.class;Class.forName("xxx");- 反射的优缺点:
- 优点:运行期间能够动态的获取类,提高代码的灵活性。
- 缺点:性能比直接的Java代码要慢很多。
- 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。
setnx key value来实现,但如果客户端忘记解锁,那么这种情况就很有可能造成死锁,但如果直接给锁增加过期时间即新增expire key seconds又会发生其他问题,即这两个命令并不是原子性的,那么如果第二步失败,依然无法避免死锁问题。考虑到如上问题,我们最终可以通过set...nx...命令,将加锁、过期命令编排到一起,把他们变成原子操作,这样就可以避免死锁。写法为set key value nx ex seconds 。 解锁就是将代表锁的那份数据删除,但不能用简单的del key,因为会出现一些问题。比如此时有进程A,如果进程A在任务没有执行完毕时,锁被到期释放了。这种情况下进程A在任务完成后依然会尝试释放锁,因为它的代码逻辑规定它在任务结束后释放锁,但是它的锁早已经被释放过了,那这种情况它释放的就可能是其他线程的锁。为解决这种情况,我们可以在加锁时为key赋一个随机值,来充当进程的标识,进程要记住这个标识。当进程解锁的时候进行判断,是自己持有的锁才能释放,否则不能释放。另外判断,释放这两步需要保持原子性,否则如果第二步失败,就会造成死锁。而获取和删除命令不是原子的,这就需要采用Lua脚本,通过Lua脚本将两个命令编排在一起,而整个Lua脚本的执行是原子的。最简单redis分布式锁的实现方式:加锁:setnx(key,1),解锁:del(key),问题:如果客户忘记解锁,将会出现死锁。第二种分布式锁的实现方式:setnx(key,1)+expire(key,30),解锁:del(key).问题:,由于setnx和expire的非原子性,当第二步挂掉,仍然会出现死锁。第三种方式:加锁:将setnx和expire变成原子性操作,set(key,1,30,NX),解锁:del(key)。同时考虑到线程A还在执行,但是锁已经到期,当线程A执行结束时去释放锁时,可能就会释放别的线程锁,所以在解锁时要先判断一下value值,看是不是该锁,如果是,再进行删除。
方案一:SETNX + EXPIRE
方案二:SETNX + value值是(系统时间 + 过期时间)
方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)
方案四:SET的扩展命令(SET EX PX NX)
方案五:SET EX PX NX + 校验唯一随机值,再释放锁
方案六:开源框架:Redisson
方案七:多机实现的分布式锁Redlock
2020.04.21
ArrayList底层是数组实现的,数组是一组连续的内存单元,读取快(使用索引),插入删除慢(需要重新计算大小或是更新索引) --底层是一个双向链表和双端队列特点,查询慢,增删快
LinkedList底层基于双向链表,读取慢,插入删除快;链表的每一个节点保存了数据值,和指向前一个节点的指针和指向后一个节点的指针,LinkedList比Array更占内存 --底层是一个双向链表和双端队列特点,查询慢,增删快
共同点;都是线程不安全的.
两者主要的区别是数据和索引的分离,聚簇不分离,非聚簇分离
它们两个的最大区别就是索引和数据是否存放在一起。
- 聚簇索引:索引和数据存放在一起,叶子节点保留数据行。
- 非聚簇索引:索引和数据分开存放,叶子节点存放的是指向数据行的地址。
索引的数据结构会被存储在磁盘中,每次查询都需要到磁盘中访问,对于红黑树,树的高度可能会非常的高,会进行很多次的磁盘IO,效率会非常低,B+树的高度一般为2-4,也就是说在最坏的条件下,也最多进行2到4次磁盘IO,这在实际中性能时非常不错的
红黑树本质上还是二叉树,一个结点最多只能拥有两个子结点,而B+树则是多叉的,这会使得相同的数据存储,二叉树的高度会大于B+树;而数据是存储在磁盘上的,树的高度越高,磁盘IO次数越多,开销越大,B+树可以有效的减少这一开销;且B+树的叶子节点是通过链表的方式进行相连的,能在找到起点和终点后快速取出需要的数据
关于时间复杂度:添加和删除都需要修改skiplist,所以复杂度为O(log(n))。 但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1) ,其他的range操作复杂度一般为O(log(n)),当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)集合中的最大成员数为 232-1 个元素(超过 40 亿个元素)。Redis主要提供了5种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)。
- 五种基本数据类型:
- String:String是Redis中最基本的数据类型,可以存储任何数据,包括二进制数据、序列化的数据、JSON化的对象甚至是图片。
- List:List是字符串列表,按照插入的顺序排序,元素可以重复,你可以添加一个元素到哦列表的头部或者尾部,底层是一个链表结构。
- Set:Set是一个无序不重复的集合。
- Hash:Hash是String类型的filed和value的集合,适合用于存储对象。
- Zset:Zset和set一样也是String类型元素的集合,且不允许有重复的元素,但不同的是Zset的每个元素都会关联一个分数,分数可以重复,Redis通过分数来为集合汇总的成员进行从小到大的排序。
- 四种特殊数据类型
- bitmap
- hyperloglog
- geo
- stream
工厂模式:不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。
- 简单工厂:提供一个统一的工厂类来创造对象,应用场景:需要创建的对象较少,客户端不关心对象的创建过程;
- 工厂方法:针对不同的对象提供不同的工厂,应用场景:客户端不需要知道它所创建的对象的类。客户端可以通过子类来指定创建对应的对象。
- 抽象工厂模式:可以创建一组对象。
2023.4.26
java.uti包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。
一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。
在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/CopyOnWriteArrayList等
(数组长度 >>> 3) / CPU核心数。 也就是说,为线程分配的迁移任务,是充分考虑了硬件的处理能力的。多个线程依据硬件的处理能力,平均分摊一部分槽的迁移工作。另外,如果计算出来的迁移数量小于16,则强制将其改为16,这是考虑到目前服务器领域主流的CPU运行速度,每次处理的任务过少,对于CPU的算力也是一种浪费。ConcurrentHashMap是一个线程安全的集合,它的底层是数组+链表/红黑树构成的。
在1.7的时候采用segment数组+hashEntry的方式实现的,lock加在Segment的上面,在size计算的时候,首先是不加锁的,最多计算三次,前后两次的结果是相同的话那么结果就是准确的,如果不一样的话,那么就加锁,重新计算。
在1.8的时候废弃了这种算法,采用Synchronized+CAS+Node来保证并发安全的进行,使用一个volatile类型的变量baseCount来记录元素的个数,集合每次增加或者删除的时候,basecount就会通过addCount()来对baseCunt产生相应的变化,最后得到元素的个数。 初始值为16,每次扩容都是之前的二倍,不支持null值和null为key
- ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树
- 采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。
- 实现机制:
- 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换
- 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。
- 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。
- 在扩容的过程中,依然可以支持查找操作。
2023.04.30
- 缓存穿透:客户端访问不存在的数据,使得请求直达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。
- 解决方式:
- 存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层直接返回空值。
- 将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。
- 缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。
- 解决方案:
- 永不过期:对热点数据不设置过期时间。
- 加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。
- 缓存雪崩:大量数据同时过期、或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。
- 解决方案:
- 避免数据同时过期,设置随机过期时间。
- 启用降级和熔断措施。
- 设置热点数据永不过期。
- 采用redis集群,一个宕机,另外的还能用
保证双写一致性共有四种方案:
- 先更新缓存,在更新数据库,该方式的优点是能够保证数据访问是都会命中redis,降低数据库压力。缺点是每次修改都会操作缓存降低了服务器的性能。
- 先更新数据库,再更新缓存:缺点是可能导致数据库和redis中的数据不一致(数据库更新成功,redis更新失败)。
- 先删除redis,再更新数据库。缺点:可能导致数据库和redis中的数据都是旧数据(删除redis后,再更新数据时失败了)
- 先更新数据库再删除redis。缺点:可能导致数据库和redis的数据不一致(redis删除失败)。出错时使用重试机制异步重新处理。
- Java通过加锁实现线程同步,锁有两类:synchronized和Lock。
- synchronized加在三个不同的位置,对应三种不同的使用方式,这三种方式的区别是锁对象不同:
- 加在普通方法上,则锁是当前的实例(this)。
- 加在静态方法上,锁是当前类的Class对象。
- 加在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。
- Lock支持的功能包括:支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量(阻塞队列)
2023.05.05
InnoDB支持事务,默认的锁是行锁。 MyISAM不支持事务,默认的锁是表锁。 读写性能:InnoDB增删改性能更优;MyISAM查询性能更优。
- innodb支持事务,默认支持外键约束,myisam不支持;
- Innodb支持行级锁(通过在索引上加锁实现);myisam不支持,支持表级锁;
- Innodb的增删改性能更优;Myisam的查询性能更优;
- Innodb不支持全文索引,myisam默认支持;
- innodb使用聚簇索引和非聚簇索引,而myisam只使用非聚簇索引,innodb不支持全文索引(可通过插件使其支持),myisam支持全文索引。
- 前者增删改性能更优,后者查询性能更优
- innodb最少需要一个文件便可以存储表信息。myisam至少需要三个文件(分别存储表结构,数据,索引)
- Mysql5.5之后默认引擎是InnoDB, 行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,innoDB才使用行锁,否则,使用表锁
AbstractStringBuilder,二者无论是构造器还是方法都基本相同,不同的一点是,StringBuilder没有考虑线程安全问题,也正因如此,StringBuilder比StringBuffer性能略高。因此,如果是在单线程下操作大量数据,应优先使用StringBuilder类;如果是在多线程下操作大量数据,应优先使用StringBuilder类。
- StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
- String:不可变字符序列,效率低,但是复用率高。
- String类型对象创建出来后归常量池管,可以随时从常量池调用同一个String对象。StringBuffer和StringBuider在创建对象后一般要转化成String对象才调用。
- StringBuffer:可变字符序列、效率较高(增删)、线程安全
- String是final修饰不可被继承不可变,即时修改也是新建一个变量,String通过不变的特性实现了线程之间的可见性,一定条件下可以保证线程安全。StringBuffer和StringBuilder对象是可变的。StringBuffer实现了Synchronize封装使得它线程安全,而StringBuilder是线程不安全的。所以从运行速度来说,StringBuilder>StringBuffer>String
- StringBuilder:可变字符序列、效率最高、线程不安全
2023.05.18
- 数据结构:在1.8之前,HashMap的底层是数组加链表,JDK8后,HashMap底层是采用“数组+链表+红黑树”来实现的;
- HashMap是基于哈希算法来确定元素的位置,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定位置。若碰撞进一步加剧会创建红黑树来代替这个链表,从而提高对数据的查找速度
- 它的put流程是:基于哈希算法来确定元素位置,当我们向集合存入数据时,他会计算传入的key的哈希值,并利用哈希值取绝对值再根据集合长度取余来确定元素的位置,如果这个位置已经存在其他元素了,就会发生哈希碰撞,则hashmap就会通过链表将这些元素组织起来,如果链表的长度达到8时,就会转化为红黑树,从而提高查询速度。
- 判断数组,若发现数组为空,则进行首次扩容。
- 判断头节点,若发现头节点为空,则新建链表节点,存入数组。
- 判断头节点,若发现头节点非空,则将元素插入槽内。
- 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。
- 扩容机制:HashMap中数组的默认初始容量为16,当达到默认负载因子0.75时,会以2的指数倍进行扩容。 Hashmap时非线程安全的,在多线程环境下回产生循环死链,因此在多线程环境下建议使用ConcurrentHashMap。
- 触发扩容行为三个条件:
- 如果数组为空,则进行首次扩容。
- 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。
- 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。
- 类加载子系统:根据全限定名来载入类;
- 执行引擎:执行那些被载入类的方法的指令;
- 运行时数据区:在程序运行时,存储程序的内容,例如:字节码、对象、参数、返回值等。运行时数据区又分为方法区、堆、栈、程序计数器
JVM的垃圾回收机制是遵循分代收集理论进行设计的,主要分为四种收集方式:1.新生代收集,目标为新生代的垃圾收集。2.老年代收集:目标为老年代的垃圾收集,目前只有CMS收集器会有这种行为。3.混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。4.整堆收集:目标为整个方法区和堆的垃圾收集。
常见的垃圾回收算法包括:标记清除算法:缺点:内存碎片化,优点:速度快。标记复制算法:缺点占用内存大,优点:内存连续。标记整理算法:优点:内存连续,缺点:整理效率低。
34 说说JVM的垃圾回收机制
35 说说类加载机制
36 epoll原理
37 请你说一下抽象类和接口的区别
38 请你说说==与equals()的区别
39 说说synchronize的用法及原理
40 说说你对AQS的理解
41 Java哪些地方使用了CAS
42 说说JVM的垃圾回收算法
43 请你说说Redis数据类型中的zset,它和set有…
44 说说static修饰符的用法
45 说说线程的状态
46 说说你对ThreadLocal的理解
47 说说Spring Boot常用的注解
48 说说Bean的生命周期
49 synchronized和Lock有什么区别
50 说说volatile的用法及原理
说说Redis的单线程架构
如何实现Redis高可用
请你说一下final关键字
请你说说重载和重写的区别,构造方法能不能…
请说说你对Java集合的了解
请你说说I0多路复用
请你说说索引怎么实现的B+树,为什么选这…
请你讲一下Java 8的新特性
请你说说泛型、泛型擦除
说说你了解的线程通信方式
请你说说JUC
请你说说HashMap和Hashtable的区别
HashMap是线程安全的吗?如果不是该如…
请你说说Java的四种引用方式
请你讲下G1垃圾回收器
请你说说内存溢出
请你说说内存泄漏
请你说说数据库引警有哪些,各自有什么区别
简单介绍Spring
介绍一下MyBatis的缓存机制
请你说说String类,以及new和
请你说说hashCode0和equals0的区别,为…
说说线程的创建方式
说说你对ArrayList的理解
请你说说BIO、NIO、O
说说你对Spring Boot的理解以及它和Spri…
说说Spring Boot的自动装配
说说@Autowired和@Resource注解的区别
说说Redis的主从同步机制
说说Redis的缓存淘汰策略
说说垃圾收集器
请你说说Java的特点和优点为什么要选择Ja…
介绍一下包装类的自动拆装箱与自动装箱
说说wt0和sleep0的区别
说说你对线程池的理解
简单说下你对JVM的了解
说说Java运行时数据区
请你讲下CMS垃圾回收器
说说JVM的双亲委派模型
请你说说数据库索的底层数据结构
说说Spring Boot的启动流程
介绍一下SpringMVC的执行流程
在MyBatis中$和#有什么区别
请你说说Java基本数据类型和引用类型
请你说说Java的异常处理机制
说说你对面向对象的理解
请介绍一下访问修饰符
说说Java中常用的锁及原理
请你说说List与Set的区别
请你讲一下Java NIO
说说GC的可达性分析
说说类的实例化过程
请你讲讲B树和B+树
MySQL主从同步是如何实现的?
请你介绍一下数据库的ACID
请你说说数据库的索引是什么结构,为什么不
请你说说InnoDB的MVCC
说说Soring Boot的起步依赖
说说Spring事务管理
说说Bean的作用域,以及默认的作用域
说说BeanFactory和FactoryBean的区别
说说你对Redis的了解
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。