文章目录
多线程
并行和并发有什么区别?
并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
线程和进程的基本概念、线程的基本状态以及状态之间的关系?
一个线程是进程的一个顺序执行流程。一个进程中的全部线程共享同一个堆空间。线程本身有一个供程序执行时的栈,一个进程中可以包含多个线程。
线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态
新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。
就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
运行状态:线程正在运行的过程中,碰到调用Sleep()方法,或者等待IO完成,或等待其他同步方法完成时,线程将会从运行状态,进入到阻塞状态。
死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。
守护线程是什么?
守护线程又称为后台线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
正常创建的线程都是普通线程,或称为前台线程,守护线程与普通线程在使用上没有什么区别,但是他们有一个最主要的区别是在于进程的结束中。当一个进程中所有普通线程都结束时,那么进程就会结束。如果进程结束时还有守护线程在运行,那么这些守护线程就会被强制结束
在 Java 中垃圾回收线程就是特殊的守护线程
创建线程有哪几种方式?
继承Thread类(真正意义上的线程类),是Runnable接口的实现。
实现Runnable接口,并重写里面的run方法。
使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。
实现Runable和实现Callable有什么区别
两者最大的区别,实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回执行结果
Callable接口实现类中run()方法允许将异常向上抛出,也可以直接在内部处理(try…catch); 而Runnable接口实现类中run()方法的异常必须在内部处理掉,不能向上抛出
sleep() 和 wait() 有什么区别?
类的不同:sleep() 来自 Thread,wait() 来自 Object。
释放锁:sleep() 不释放锁;wait() 释放锁。
用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
线程的 run() 和 start() 有什么区别?
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
run() 可以重复调用,而 start() 只能调用一次。
第二次调用start() 必然会抛出运行时异常
线程池的七个参数
一、corePoolSize 线程池核心线程数
二、maximummPoolSize:线程池最大线程数量
三、KeepAliveTime:空闲线程存活时间
四、unit :时间单位
五、workQueue: 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory: 线程工厂
七、handler :拒绝策略
①CallerRunsPolicy
只要线程池没有关闭,该策略直接在调用者线程中,运行当前被丢弃的任务,虽然不会真的丢弃任务,但是任务提交线程的性能极有可能急剧下降。
②AbortPolicy(默认)
使用该策略会直接抛异常,阻止系统正常工作,抛出RejectedExecutionException异常。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
拒绝时机
当调用shutdown等方法关闭线程池的时候,如果此时继续向线程池提交任务,就会被拒绝。
当任务队列已满,而且线程达到最大线程数,如果再添加任务,就会被拒绝
只要任务数<=核心线程数+队列长度;工作的线程=核心线程数
创建线程池有哪几种方式?
1.newSingleThreadExecutor():单线程的线程池
2.newFixedThreadPool(int nThreads):额定数量的线程池
3.newCachedThreadPool():大量线程的线程池
4.newScheduledThreadPool(int corePoolSize):定时启动的线程池
newSingleThreadExecutor():单线程的线程池
它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
newCachedThreadPool():大量线程的线程池
它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
newFixedThreadPool(int nThreads):额定数量的线程池
重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
newScheduledThreadPool(int corePoolSize):定时启动的线程池
和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
为什么使用线程池?
由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存。
在 Java 程序中怎么保证多线程的运行安全?
使用安全类,比如 Java. util. concurrent 下的类。
使用自动锁 synchronized。
使用手动锁 Lock。
锁
什么是死锁?怎么防止死锁?
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
防止死锁方法:
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
尽量使用 Java. util. concurrent 并发类代替自己手写锁。
尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
尽量减少同步的代码块。
synchronized 和 volatile 的区别是什么?
volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
synchronized 和 Lock 有什么区别?
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
synchronized 和 ReentrantLock 区别是什么?
ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。
Lock 底层原理
一种是关键字:synchronized,一种是concurrent包下的lock锁。
synchronized是java底层支持的,而concurrent包则是jdk实现。
微服务
微服务的框架演变
单体架构---->负载均衡------>读写分离------>全文检索和缓存
微服务组件
RestTemPlate:
是一个服务器之间的请求工具,可以自动实现josn的序列化和反序列化。
(对象序列化和反序列化
对象序列化:将对象状态信息持久化保存的过程。
反序列化:根据对象的状态恢复对象的过程。
)
Eureka:服务的注册和发现,
治理中心:eureka服务器就是治理中心,它里面要注册所有的微服务,会生成一个服务列表。
服务注册:eureka客户端要连接注册中心,将自己的信息注册到服务器列表。
心跳机制:注册中心每隔秒连接一次客户机,如果多次连接没有响应,就会从服务列表中把相关的服务移除。
自我保护机制:如果在15分钟内超过85%的客户端节点都没有了心跳,那么Eureka就会认为客户端与注册中心出现了网络故障,Eureka会自动进入自我保护状态,不在注销任何实列
Ribbon:负载均衡工具
Ribbon是一个客户端负载均衡工具,客户端负载均衡工具不同于Nginx负载均衡的,
Ribbon和应用程序绑定,本身不储存服务器列表,运行时会通过程序获取注册服务列表,然后通过列表进行负载均衡和调用。
Nginx:独立进程做负载均衡,通过负载均衡策略,将请求转发到不同的服务器上。
客户端负载均衡,通过在客户端保存服务器列表信息,然后自己调用负载均衡策略,分摊那调用不同的服务。
实现策略:因为ribbon有个RestTemPlate的拦截器,当RestTemPlate发送请求时,会根据自身负载策略。然后对请求路径进行替换,
OpenFeign:服务调用
可以简化开法
OpenFeign可以替代 RestTemPlate和Ribbon
Hystrix:断路器
服务的雪崩
名词解释
服务雪崩效应是一种因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程。
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的 “扇出” 。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,
进而引起系统崩溃,这就是所谓的“雪崩效应”。
雪崩的原因
- 服务提供者不可用的原因
- 硬件故障
- 程序Bug
- 缓存击穿
- 用户大量请求
秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
- 服务调用者不可用的原因是同步等待造成的资源耗尽,服务调用者会产生大量的等待线程占用系统资源,一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态。
应对雪崩的办法
熔断/降级
熔断机制相当于电路的跳闸功能。正常情况下断路器关闭可正常请求服务,当一定时间内请求达到了一定的阈(yu)值(请求失败率达到百分之50或者100次/分钟),
断路器就会打开此时不会再去请求依赖服务。断路器打开一段时间之后,会自动进入半开的状态。
此时断路器允许一个请求请求服务实例,如果该服务可以调用成功,则关闭断路器,否则继续保持打开状态。
在有限的资源情况下,为了能抗住大量的请求,就需要对系统做出一些牺牲,放弃一些次要功能,优先保证主要功能能够正常运行。比如微博的评论功能,购物网站的推荐商品。
限流/隔离
限流,顾名思义,就是限制某个微服务对资源的使用量比如可用线程或者对流量拒接服务等。
Hystrix断路器状态
A. 关闭状态:整个微服务系统中的微服务的调用时正常的。断路器是没有任何作用的
B. 打开状态:当一个微服务调用另外一个微服务时,发生了问题(不能正常请求),就会触发断路器,让断路器是一个打开状态。一旦断路器,对微服务的请求就会做回退处理
C. 半开状态:断路器打开一段时间后,会允许一个请求调用微服务;这个状态被称为“半开”状态。如果这个请求能够正常调用,断路器会切换为关闭状态;如果这个请求不能正常调用,断路器继续处于打开状态
Gateway网关
网关,在微服务的架构中,主要是用来路由,增强和控制对服务的访问,同时也具备负载均衡,流量控制,访问控制等能力
为什么使用
- 微服务网关封装了应用程序的内部结构,客户端只需要跟网关交互,而无需直接调用特定微服务
- 方便监控,所有的请求都可以先请求网关,方便了对请求的管理和监控。
- 统一过滤处理,方便管理。
config配置中心
为什么要统一管理微服务配置
配置是程序的一部分,配置设计得好的话,程序就会非常灵活、易用。把易改变的配置,比如选项、 开关、阈值、密码等,从代码中独立出来,从外部去加载。这样做的主要好处有两个,一个是改变配 置,不需要重新编译打包应用程序;第二配置可以做到动态变更。
事务特性
原子性:在同一个事务的sql不可分割,要不同时成功,要不同时失败
一致性:一旦提交事务,在回滚段中的内容会完全复制到原始数据中,提交之前的查询,与提交之后的 查询结果一致
隔离性:在事务没有提交或者回滚时,其他用户不可得知事务中的内容
持久性:一旦提交或者回滚事务,事务堆数据库的影响是永久的不可修复的
Redis
Redis的过期策略和内存淘汰是什么东西
过期策略和内存淘汰看着相似,却不是一回事。往redis里面写入数据会消失是正常的。在生产环境的redis经常会丢失一些数据,写进去了,过一会可能就没了?这是因为redis是缓存不是数据库。
缓存主要存在内存中,内存空间是有限的,比如redis只能存10G,你要是往里面写20G的数据,redis会清除掉10G的数据,来保存另外10G的数据,清除那些数据是根据热度清除不常用的数据。
redis过期策略
在redis中过期的key不会立马删除,而是会同时以两种策略进行删除
定期删除:每隔一段时间,随机检查设置了过期的key并删除已经过期的key,维护定时器消耗CPU资源;
定期删除:redis每隔100m进行一次过期扫描
1.随机取20个设置了过期策略的key;
2.检查20个key中过期时间中已过期的key并删除;
3.如果超过25%的key已过期则重复第一步;
这种循环随机操作会连续到过期的key可能仅占全部key的25%以下时,并且为保证不会出现循环过多的情况下,默认扫描时间不会超过25ms;
惰性删除: 当key被访问时检查该key的过期时间,若已经过期则删除;已过期未被访问的数据仍保存在内存中,消耗内存资源;
惰性删除
定期删除可能会导致很多过期的key到了时间并没有被删除掉(如果过期的超过25%,说明过期的数据很多,删除后再取20个,接着清理,相当于递归,尽可能保持过期数在25%以下,这样就可能有些过期的一直取不到),这个时候就用到了惰性删除,当获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
获取key的时候,如果此时key已经过期,就删除,不会返回任何东西。
但实际上这还是有问题的,如果定期删除漏掉了很多过期的key,然后你也没有及时去查,也就没走惰性删除。此时会怎么样?如果大量过期key堆积在内存里,会导致redis内存块耗尽,这个时候就要用到redis的内存淘汰机制
内存淘汰机制
reids的内存淘汰策略是指在redis的用于缓存的内存不足时,怎样处理需要新写入且需要申请额外空间的数据,如何淘汰旧数据给新数据腾出内存空间。
redis内存淘汰机制:
1.noeviction:当内存不足以容纳新写入的数据时,新写入的操作会报错
2.allkeys-lru:当内存不足以容纳新写入的数据时,在键空间中,移除最近最少使用的key(最常用的)
3.allkeys-random:当内存不足以容纳新写入的数据时,在键空间中,随机移除某个key
4.volatile-lru:当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,移除最近最少使用的key
5.valation-random:当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,随机移除某个key
6.volatile-ttl:当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据,过期策略用于处理过期的缓存数据。
redis的内存用完了会发生什么实际问题?
如果达到设置的上限,redis的写命令会返回错误信息(读命令还是可以正常返回)。或者可以配置内存淘汰机制,当redis达到内存上限时会刷掉基于的内容。
redis如何做内存优化
可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的key-value可以用更紧凑的方式存放在一起。尽可能使用散列表(hashes),散列表(散列表里面存储的数少)使用内存非常小,所以应该尽可能的将数据模型抽象到一个散列表里面。比如web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该吧这个用户的所有信息存储到一张散列表里面。
reids的优势
1.读写性能优异,redis读的速度是11W/S,写的速度是8.1W/S。
2.主持数据持久化,支持AOP和RDB两种持久化方式。
3.支持事务,redis的所有操作都是原子性的,同时redis还支持对几个操作合并的原子性执行。
4.数据结构丰富与,除了支持string类型的value外还支持hash,set,zset,list等数据结构。
5.支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
6.支持大量集群节点。
为什么使用redis
redis是Nosql数据库中使用较为广泛的非关系型内存数据库,redis内部是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型,类似于Java中的map)。Redis基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
缓存穿透
查询一个在数据库中不存在的数据,查询的时候缓存中也不会有这个数据,所以该请求查询缓存,没有数据,紧接着查询数据库,如果不做任何的措施,每一次查询都会直接去数据库,缓存就失去了减轻服务器压力的意义。‘
要查询的数据在数据库中不存在
解决方案:
- 空值缓存
第一个查询,数据库没有,就在缓存中添加一个空值的记录,例如:订单号-null
后续的所有查询,会直接从缓存中获取空值,不会再查询数据库 - 布隆过滤器
一种数据结构的工具,redis可以安装。
可以快速的判断某个数据在一个集合中存不存在
缓存雪崩
缓存雪崩是指在我们设置缓存时多个key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,数据瞬时压力过大崩溃。
大量数据同一时间失效
解决方案:
- 随机失效时间
缓存击穿
对于一些设置了过期时间的key,如果这些key是热点数据。
碰巧访问量最大的时候key正好失效了,那么所有对这个key的数据查询就会直接转向数据库,我们称为缓存击穿。
热点数据失效
解决方案:
- 永不失效
- 加锁:大并发的时候即便是失效了也只能一个人查询数据库,然后重建缓存
本地锁 JDK锁 在JVM Lock
分布式锁 逻辑锁 代码逻辑上的实现
Redis持久化机制
AOF:把redis的变更追加到日志中。
优点:redis每一次的改变都会记录到日志中,可以详细的记录redis数据改变
缺点:日志文件比RDB数据快照文件大,恢复数据也慢。
RDB:RDB持久化是通过快照方式来完成的 。
优点:可以更快的来回复redis的数据
缺点:RDB是以快照方式,间隔一段时间,或者更长时间生成一次,这个时候如果redis进程宕机,那么就会丢失这一段时间的数据。
集合框架
ArrayList
有序有下标、元素可重复、底层数组实现、查询快增删慢、线程不安全,效率高。
ArrayList集合初始容量为的数组,开始添加元素数组自动增长为10,扩容长度为原来 的1.5倍
LinkedList
有序有下标、元素可重复、底层链表实现、查询慢增删快,线程不安全
LinkedList是一个双向链表,没有初始大小,也没有扩容机制,就一直在前面后者在后面一直新增就行
HashSet
无序无下标,元素不可重复底层哈希表实现
怎么保证元素不重复
比较元素内容是否相同,使用equals(),为了避免equals()多次执行,使用HashCode()触发equals()执行,
HashCode(),返回的哈希码值相同,进行内容对比,返回true,不添加
HashCode(),返回哈希码值不同,不在执行equals(),直接添加
equals() 相等,hashCode()一定相等。
两个对象的hashCode()相等,equals()不一定相等。
LinkedSet
可以记录插入顺序的set集合,底层哈希表+链表实现
HashMap
键值对存储,键不可以重复,值可以重复,底层哈希表实现,允许null键和null值
如何存值
HashMap初始长度为16,当添加元素时,计算对象键的哈希码值,对数组长度取余,余数为元素插入的下标,如该位置为null,直接添加。
若该位置有值,进行哈希码值比较,不同则拉链存储。
若相同,调用equals()进行键对比,键相同覆盖原有值,不同拉链存储
HashMap加载因子0.75,扩容长度为当当前一倍。
HashTable
键值对存储,键不可以重复,值可以重复,底层哈希表实现,不允许null键和null值 ,线程安全,效率低;
扩容
默认加载因子0.75,扩容长度为原数组的2倍加1
怎么实现Map有序
使用使用LinkedHashMap 或 TreeMap
LinkedHashMap内部维护了一个单链表,有头尾节点,有before和after用户标志前后接节点。可以实现按插入的顺序或访问顺序排序
TreeMap实现了SortedMap的接口,它的key是一个有序的Map类
Set 和 List 效率上对使用LinkedHashMap 或 TreeMap 比怎么样呢?
set删除和插入效率高,插入和删除不会引起元素位置改变,
HashMap之1.7和1.8的区别
JDk1.7 | JDk1.8 | |
---|---|---|
存储结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
插入数据方式 | 头插法(先讲原位置的数据移到后1位,再插入数据到该位置) | 尾插法(直接插入到链表尾部/红黑树) |
扩容时机 | 插入数据之前扩容 | 插入数据成功之后扩容 |
存放数据的规则 | 无冲突时,存放数组;冲突时,存放链表 | 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树 |
1.底层数据结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构(当链表长度大于8,转为红黑树)。
JDK1.8中resize()方法在表为空时,创建表;在表不为空时,扩容;而JDK1.7中resize()方法负责扩容,inflateTable()负责创建表。
2.1.8中没有区分键为null的情况,而1.7版本中对于键为null的情况调用putForNullKey()方法。但是两个版本中如果键为null,那么调用hash()方法得到的都将是0,所以键为null的元素都始终位于哈希表table中。
当1.8中的桶中元素处于链表的情况,遍历的同时最后如果没有匹配的,直接将节点添加到链表尾部;而1.7在遍历的同时没有添加数据,而是另外调用了addEntry()方法,将节点添加到链表头部。
1.7中新增节点采用头插法,1.8中新增节点采用尾插法。这也是为什么1.8不容易出现环型链表的原因。
1.7中是通过更改hashSeed值修改节点的hash值从而达到rehash时的链表分散,而1.8中键的hash值不会改变,rehash时根据(hash&oldCap)==0将链表分散。
1.8rehash时保证原链表的顺序,而1.7中rehash时有可能改变链表的顺序(头插法导致)。
在扩容的时候:1.7在插入数据之前扩容,而1.8插入数据成功之后扩容。
MashMap为什么线程不安全
在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,
导致在get时会出现死循环,所以HashMap是线程不安全的。
为什么选择ConcurrentHashMap?
在并发编程中使用HashMap可能会造成死循环,而使用线程安全的HashTable效率低下,基于这两个原因,ConcurrentHashMap有了登场的机会
ConcurrentHashMap的分段锁技术可有效提升并发访问率,ConcurrentHashMap首先会将数据一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。
对于 ConcurrentHashMap 你至少要知道的几个点:
默认数组大小16
扩容因子0.75,扩容后的长度大小翻倍
当储存的node总数量>=数组长度*扩容因子,会进行扩容(node总数量是指所有put进map的node数量)
当链表长度>=8且数组长度<64时进行扩容
当数组下是链表时,在扩容的时候会从链表的尾部开始rehash
当链表长度>=8且数组长度<=64时链表会变成红黑树
当一个事务操作发现map正在扩容时,会帮忙扩容
SQL优化
数据库优化手段:
1.索引优化
索引特点:索引会占用内存空间比较小。MySql会为主键自动增加索引。MySql数据库做增删改操作时也会维护索引,索引也会影响数据库增删改效率
使用原则:
一般对Sql中的where字段或者order by字段建立索引
2.添加查询缓存
3.优化SQl语句
在表中建立索引,优先考虑where,group by 使用的字段。
避免使用select * ,使用具体字段代替*
尽量不要使用 in和 not in,会导致数据库放弃索引进行全表扫描。
尽量不要使用or 会导致数据库放弃索引进行全局扫描
尽量不要使用字段开头模糊查询,会导致数据库放弃索引进行全表扫描
解决办法:在字段后面使用模糊查询。
尽量避免进行null值的判断,会导致数据库索引擎放弃索引进行全表扫描。
解决办法:给null字段添加默认值0,
用>=代替>用<=代替<
两者的区别时,假如查询大于等于4的,前者会直接跳到等与4那里,后者时先找到等于3并且向前扫描大于3的记录。
尽量使用大写字母代替小写字母,虽然数据库不区分大小写,但是执行SQL语句的时候,数据库会把小写字母转化为大写字母。
尽量不要使用!=号,因为这数据库会放弃使用索引。
SpringMVC运行原理
Spring Bean的生命周期(概念重点)
Spring工厂中Bean的生命周期并不像想象的那么简单,Spring对工厂中的Bean的生命周期进行了细致 的划分,并允许开发者通过编码或配置的方式定制Bean在生命周期的各个阶段的操作。
- 初始化阶段:在对象创建后,进行一些初始化操作,比如对属性值的检查
- 销毁阶段:在回收对象之前,执行一些销毁操作,比如资源的释放
@Resource和@Autowired区别
@Resource默认按照By name自动注入的
@Autowired默认按照By type自动注入的
by name是根据属性名
by type是根据类型
Maven是什么?
Maven 是专门用于构建和管理Java相关项目的工具。
使用Maven管理项目的好处主要有两点,其一是使用Maven管理的Java 项目都有着相同的项目结构。 有一个pom.xml 用于维护当前项目都用了哪些jar包;所有的java代码都放在 src/main/java 下面; 所有的测试代码都放在src/test/java 下面 。
mySQL索引
Mysql有两种类型的索引,一种是HASH,一种是BTREE【tri:】,大多数时候我们都选择BTREE索引。BTREE索引实际上是一个B+树,B+树的前提还要从B树说起。
B树不属于二叉树,B树可以拥有大于2的子节点,所以说B树是一个平衡多叉树,一个X叉的B树的特点是:
树上的每一个节点对多包含m个子节点
所有的叶子节点都处于同一层。
当根节点不是叶子节点时,根节点至少要有2个子节点;
B+树的特点:
一棵m叉B树的每个节点最多是有m-1个key,而B+树每个节点最多可以有m个key;
B+树的叶子节点处于同一层,并且相邻的叶子节点互相连接,使所有叶子节点形成一个链表
MySql索引一般什么情况下会失效
1、如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
2、对于多列索引,不是使用的第一部分,则不会使用索引
3、like查询是以%开头,索引失效;以%结尾,索引有效
4、如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
5、如果mysql估计使用全表扫描要比使用索引快,则不使用索引
MySql的储存引擎;
MyISAM和InnDB的区别:
- InnoDB支持事务,MyISAM不支持
- InnoDB支持外键,而MyISAM不支持
- Innodb不支持全文索引,而MyISAM支持全文索引
- InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
MyISAM存放的方式:MyISAM这种引擎不支持事务,不支持行级锁,只支持并发插入的表锁,主要用于高负载的select.
索引的方式:MyISAM也是使用B+tree索引但是和InnoDB的在具体实现上有些不同
优缺点:MyISAM的优势在于占用空间小,处理速度快,缺点就是不支持事务的完整性和并发性
InnoDB存放的方式:innodb支持自增长列(auto_increment),自增长列的值不能为空,如果在使用的时候为空的话会进行自动存现有的值开始增值,如果有但是比现在的还大,则就保存这个值.
innodb存储引擎支持外键(foreign key),外键所在的表称为字表而所依赖的表称为父表.
innodb存储引擎最重要的是支持事务,以及事务相关联的功能.支持mvcc的行级锁
索引的方式:innodb存储引擎使用的是B+Tree
优缺点:InnoDB的优势在于提供了良好的事务处理,崩溃修复能力和并发控制,
缺点是读写效率较差,占用的数据空间相对较大.
如何自定义注解?
1: Annotation 型定义为@interface
:2:自定义注解需要使用到元注解
3:参数成员只能用public 或默认(default) 这两个访问权修饰 .
4:参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
5:要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
Servlet
Servlet生命周期
第一次访问该Servlet对象时被创建
每访问一次,使用一次
服务器关闭时,被销毁
Servlet三大作用域
request 啥都可以存,可以存无数个,可以存一次请求这么久
session 啥都可以存,可以存无数个,可以存一次会话
ServletContext 啥都可以存,可以存无数个,可以存tomcat服务开始,tamcat服务接受
java类加载的生命周期
加载—》连接–》准备–》解析—》初始化—》使用----》卸载
JVM的栈溢出和堆溢出
堆溢出
jvm运行java程序时,如果程序运行所需要的内存大于系统的堆最大内存,就会出现堆溢出问题。
栈溢出
虚拟机在扩展栈深度时,无法申请到足够的内存空间。
内存溢出和内存泄露
内存溢出
申请内存空间,超出最大堆内存空间。
内存泄露
其实包含内存溢出,堆内空间被无用对象占用没有即使释放,导致占用内存,最终导致内存泄露
情况:静态static修饰对象
解决:减少常量的定义