- spring中有哪些方式可以把Bean注入到IOC容器
- 项目的技术架构是怎么设计的
- 定时任务的重复和调度是怎么解决的
- 延迟任务怎么样去存储和运行
- springCloud alibaba是怎么理解的
- nacos的动态更新怎么实现的
- mysql为什么用b+tree作为索引结构
- reentrantLock的实现原理
- 数据库连接池他有哪些关系参数
- 线程池的理解
- mysql的事务隔离级别
- 什么是可重入锁
- spring中beanFactory和FactoryBean的区别
- redis存在线程安全问题吗
- springbean的作用域有哪些
- mybatis里面的缓存机制
- spring中事务传播行为
- spring框架的看法
- 谈谈你对线程安全的理解
- 什么是聚集索引和非聚集索引
- ArrayBlockingQueue的原理
- threadlocal及实现原理是什么
- wait和notify为什么要在synchronized代码块中
- 缓存雪崩和缓存穿透的理解
- volatile关键字有什么用
- 分布式锁的理解和实现
- 死锁发生的原因和避免
- springBoot自动装配机制的原理
- redis和mysql如何保证数据一致性
- CAS机制
- concurrentHashMap实现原理
- hashMap如何解决哈希冲突
- 线程池如何知道一个线程的任务已经执行完成
- lock和synchronized的区别
- seatad 的理解
好的,把bean注入到IOC容器里面我知道有7种方法
1.使用xml的方式生命Bean的定义,Spring容器在启动的时候会加载并解析这个xml,把bean装在到IOC容器中.
2.使用@CompontScan注解来扫描声明@controller @service @Repository @componet注解的类 然后把这些类加载到IOC容器里面
3.使用@configuration注解声明配置类,并使用@bean注解实现bean定义,
这种方式其实就是xml配置方式的一种演变,是spring迈入到无配置化时代的里程碑
4.使用@import注解,导入配置类或者普通的bean
5.使用factorybean工厂bean,动态构建一个bean实例,springcloud openfeign里面的动态代理实例就是使用Factorybean来实现的
6.实现importbeandefinition-Registrar接口,可以动态注入bean实例,这个在springboot里面的启动注解有用到
7.实现importselector接口,动态批量注入配置类或者bean对象,这个在springboot里面的自动个装配机制里面有用到
springcloud alibaba这么一套组件
nacos做分布式注册与配置中心
openfeign+ribbon 做远程RPC调用
hystrix做服务熔断机制
gatewat做网关
springSecurity+Oauth2.0协议做一套分布式认证服务
xxl-job 分布式定时任务组件
基于脚本的调用的自动化运维的中间件 saltstack 部署在K8S容器中,能操作这些容器中的这些mysql
调度重复的话,本身xxl-job他本身是自带一个基于DB的这种分布式锁,但是还不太够,这里要用redission分布式锁来实现,他有一个看门狗的机制,本来要设置30秒的超时时间,你就会每10秒钟去看一次,看这个程序有没有跑完,没跑完就继续,给你的这个锁的key继续续期,框架已经实现好了.
延迟任务怎么样去存储和运行redission里面运用了一个时间轮的机制来实现的
springCloud alibaba是怎么理解的基于spring cloud netflix 的进一步改良,比如nacos替换了eureka
有自己的生态比如有nacos注册中心,配置中心,sentinel做服务的限流熔断,seata做分布式事务,
通过长轮询的定时机制去拉取nacos上这些的配置文件,然后去看他的MD5的这个编码,知道他的这个配置文件是不是有新的变化,新的更新
mysql为什么用b+tree作为索引结构关于这个问题我会从几个方面来回答,首先常规的数据库存储引擎,一般都是采用B树,或者B+树来实现索引的存储,因为B树呢是一种多路平衡树,用这么存储结构来存储大量数据的情况下呢,他的整体高度相比二叉树来说,会矮很多,而对于数据库来说,所有数据必然是存储在磁盘上的,而磁盘IO的效率是很低的,特别是在随机磁盘IO的一个情况下,效率更低,所以树的高度就决定了磁盘IO的一个次数,磁盘IO次数越少,对性能的提升就会越大,这也是为什么采用B树作为索引存储结构的原因.
但是在mysql的innodb存储引擎里面他采用的是一种增强的B树结构,也就是B+树,相对于B树,B+树做了这个几个优化,首先是
1.B+树的所有结构数据都存放在叶子节点上,而非叶子节点只会存储索引
2.叶子结点中的数据使用双向链表方式进行关联
所以我认为用B+树作为索引结构有以下几个原因
1.B+树非叶子结点不存储数据,所以每一层能够存储的索引数量增加,意味着B+树在层高相同的情况下存储的数据要比B树要多,使得磁盘IO次数更少
2.mysql中,范围查询是一个比较常用的操作,而B+树的所有存储在叶子结点的数据使用了双向链表来关联,所以在查询的时候只需查两个结点进行遍历就行,而B树需要获取所有节点,所以B+树在范围查询上效率更高.
3.在数据检索方面,由于所有的数据都存储在叶子节点,所以B+树的IO次数会更加稳定一些
4.因为叶子结点存储所有数据,所以B+树的全局扫描能力更强一些,因为他只需要扫描叶子结点,但是B树需要遍历整个树
另外基于B+树这样一个结构,如果采用自增的整形数据作为主键,还能更好的去避免增加数据的时候,带来的叶子结点的分裂,导致大量运算的一个问题
reentrantlock是一种可重入的排它锁,他主要是解决多线程对于共享资源的竞争的一个问题,他的核心特性有
1.他支持可重入,也就是获得锁的线程在释放锁之前再次去竞争同一把锁的时候,不需要加锁就可以直接访问
2.支持公平和非公平的特性.
3.他提供了阻塞竞争锁和非阻塞竞争锁的两种方法,分别是lock(),trylock()
他底层用到了几个非常关键的技术
1.锁的竞争,利用互斥变量,使用CAS机制来实现的
2.没有竞争到锁的线程啊 abstractQueueSybchronizer这样一个队列同步器来存储,底层是通过双向链表来实现,当锁被释放之后呢,会从AQS队列里面的头部,去唤醒下一个线程
3,公平和非公平的一个特征,主要是体现在竞争的时候,公平锁是否需要判断AQS队列里面是否有等待线程
而非公平锁是不需要去判断的
最后关于锁的重入性,在AQS里面有一个成员变量来保存,当前获取锁的线程,当同一个线程,再次来竞争同一把锁的时候,那么不会去走锁的竞争逻辑,而是直接去增加重复次数.
数据库的连接池是一种池化技术,池化技术的核心思量是实现资源的一个复用,避免资源的重复创建和销毁,带来的一个开销,而在数据库的应用场景里面呢,应用程序每一次向数据库发起CRUD操作的时候,都需要去创建连接,而在数据库访问量比较大的情况下呢,频繁的创建链接会带来比较大的性能开销,而连接池的核心思想就是,应用程序在启动的时候,去提前初始化一部分的链接,保存在连接池里面,当应用程序需要用到连接去进行数据操作的时候,直接从连接池里面取出一个已经建立好的链接,进行操作就好了.所以连接池的设计避免了每一次连接的建立和释放,带来的一个开销.
连接池初始化关键参数:
1.初始化连接数,表示启动的时候初始多少个连接保存到连接池里面
2.最大连接数,表示同时最多能支持多少连接,如果连接数不够,后续要获取连接的线程会阻塞
3.最大空闲连接数,表示没有请求的时候,连接池中要保留的最大空闲连接.
4.最小空闲连接,当连接数小于这个值的时候,连接池需要再创建链接来补充这个值
连接池使用时的关键参数:
1.最大等待时间,就是连接池里面的链接用完了,新的请求要等待的时间,超过这个时间就会提示超时异常.
2.无效连接清除,清理连接池里面的无效连接,避免使用这个连接操作的时候出现错误
线程池本身就是一种池化技术,池化技术是资料复用的一种思想,比较常用的池化技术有连接池,内存池,对象池.
而线程池复用的是线程资料
他的核心设计目的我认为有2个
1.减少线程的频繁创建和销毁带来的性能开销,因为线程创建会涉及到CPU上下文切换,内存分配等工作
2.线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用
而实现了线程的复用,线程池用到了阻塞队列,线程池里面的工作线程处于一直运行状态,它会去从阻塞队列里面去获取待执行的一个任务,一旦队列空了,那么这个工作线程就会被阻塞,直到下一次有新的任务进来,工作线程是根据任务的情况来决定阻塞或唤醒,从而去打到线程复用的一个目的.
线程里面的资源控制是通过几个关键参数来确定的
核心线程数 默认长期存在的工作线程
最大线程数 根据任务的情况来动态创建的线程,提高阻塞队列中任务的处理效率
事务的隔离级别是为了解决多个并行事务竞争导致的数据安全问题的一种规范,具体来说多个事务竞争可能会产生三种不同的一个现象
1.假设两个事务,T1 T2同时在执行,那么T1事务可能读取到T2事务未提交的数据,但是未提交的事务T2有可能会产生回滚,也就是导致T1事务读取到一个最终不一定存在的数据,从而产生一个脏读的现象
2.假设两个事务,T1 T2同时在执行,那么事务T1在不同的时刻读取的同一行数据的时候,有可能结果会不一样,从而导致一个不可重复读的一个问题.(修改)
3.假设两个事务,T1 T2同时在执行,事务T1在执行范围查询的时候,或者范围修改的时候,事务T2呢插入了一条属于事务T1范围内的数据,并且提交了,那么事务T1查询的时候发现多了一条数据,或者T1事务发现这条数据并没有被修改,看起来产生了幻觉,我们称之为幻读.(增加)
在实际应用里面呢,可能有写场景不能接受,某些现象的存在,所以在SQL标准里面定义了四种隔离级别
1.读未提交 可能会产生脏读,不可重复读,幻读
2.读已提交 可能会产生不可重复度和幻读
3.可重复读 可能产生幻读
4.串行化 多个并行事务串行化执行,不会产生安全性问题 性能最低
mysql 默认隔离级别 RR(可重复读)
可重入是多线程并发里面的一个概念,在运行的某个函数或者代码,因为抢占资源或者中断,导致这个函数或者代码运行过程中被中断了,那么等到中断的程序执行结束以后,重新进入到这个函数代码里面,在运行的时候,并且运行的结果不会发生变化.那么这个函数或者代码就是可重入的
可重入锁:
简单来说就是一个线程,如果抢占到了互斥锁的资源,在锁释放之前,再去竞争同一把所的时候,不需要等待,只需要去记录重入次数.
绝大多数的锁都是可重入的,比如说synchronized,reentrantLock
不支持重入锁的有 stampedLock
锁的可重入性主要解决问题是避免死锁的问题
因为一个获得同步锁的X的线程,在释放X之前再次去竞争锁X的时候,相当于等待自己的一个锁释放的一个情况,这很显然无法成立,会导致死锁.
以上就是我对这个问题的一个理解
1.breanfactory是所有springbean容器的顶级接口,他为spring的容器定义了一套规范,并提供像getbean这样的方法从容器中获取指定的bean的实例
2.beanFactory在产生bean的同时,还提供了解决bean之间的依赖注入的能力,也就是所谓的DI.
而factorybean是一个工厂bean,他是一个接口,他的主要功能是动态去生成某一类型bean的一个实例,也就说我们可以自定义一个bean并且加载到IOC容器里面,他里面有一个重要的方法getObject(),这个方法就是用来实现动态构建bean的一个过程.
springcloud里面openfeign的组件,客户端代理就是使用了factorybean来实现的
从redis服务端层面,redisserver本身是一个线程安全的K-V数据库,redis-server去执行指令的时候不需要任何的同步机制,不会存在线程安全的问题
redis6.0里面加入的多线程模型,只是去处理网络IO事件,对于指令的执行过程仍然是采用主线程来处理,不会存在多个线程同时去操作一个指令的情况.
redisserver没有采用多线程来执行指令,我认为呢
1.redis server本身可能出现的性能瓶颈点无非就是网络IO,CPU,内存.但是CPU不是redis的瓶颈点,所以没必要使用多线程来执行指令.
2.如果采用多线程,意味着对于redis的所有指令操作,都必须要考虑到线程安全问题,也就是说需要枷锁来解决,这种方式带来的性能影响反而更大
从redis客户端来说
虽然redis server中的指令操作是原子的,但是如果有多个redis客户端同时执行多个指令的情况下呢,就无法无保证原子性了
假设有2个redisclient同时去获取redis server上的key1时,同时去进行修改和写入,因为多线程下的原子性无法被保障,以及多进程的情况下共享资源访问的一个竞争问题,使得数据的安全性无法得到保障
客户端的线程安全性的问题解决方法有
1.尽可能使用redis的原子指令
2.对于多个客户端的资源访问去加锁
3.通过lua脚本来实现多个指令的执行
1.singleton 单例 意味着整个spring容器中只会存在一个bean实例
2.prototype 原型 意味着每次从IOC容器去获取指定bean的时候,都会返回一个新的实例对象
但是在基于spring框架下的web应用里面
增加了一个会话维度来控制bean的声明周期这样一个功能
1.request
针对每一次http请求都会创建一个新的bean
2.session
以session会话为维度,同一个session共享同一个bean实例,不同的session产生不同的bean实例
3.globalsession
针对全局session维度共享同一个bean实例
mybatis设计了一个二级缓存来提升数据检索效率,避免每一次都去查询数据库
一级缓存是sqlsession级别的一个缓存,也叫本地缓存,因为每一个用户在执行查询的时候,都需要使用sqlsession来执行
避免每一次都去查询数据库,mybatis把查询出来的数据缓存到sqlsession的本地缓存里面,后续的sql如果在命中缓存的情况下,就可以直接从本地缓存里面去取了
跨sqlsession这样的一个查询,一级缓存是无法做到的,所以mybatis设计了一个二级缓存,也就是说当多个用户在查询数据的时候,只要有任何用户数据拿到了sqlsession的数据,就会放入到二级缓存里面,那么其他的sqlsession就可以直接从二级缓存里面去加载数据了
一级缓存原理:
在sqlsession里面会持有一个executor,每个executor里面会有个叫localcache的一个对象,当用户发起查询的时候,mybatis会去根据执行语句,在localcache里面去查,如果命中了就把数据返回,如果没有命中再去数据库里面去查询出来,再写入到localcache里面
所以一级缓存的生命周期为sqlsession,所以在分布式环境或者在多个sqlsession的环境下,可能会因为一级缓存造成脏读的问题
二级缓存原理:
在原理的executor上面做了一个装饰,引入了cachingexecutor这样的一个装饰器,所以在进入一级缓存的查询之前呢,会先通过cachingexecutor进行二级缓存的查询,开启二级缓存之后,会被多个sqlsession共享,所以她是一个全局的缓存,所以她的查询流程为先查二级缓存,再去查一级缓存,然后再去查数据库
二级缓存要实现实体类的序列化
事务的传播行为是指多个事务在相互调用的时候,事务去如何传递
比如说methodA()去调用methodB(),事务怎么去执行,就取决于事务传播的一个行为
spring定义了7中传播行为
required 默认 如果说当前存在事务,就加入到这个事务里面去执行,如果不存在事务,就新建一个事务
required_new 不管存不存在事务,都会新建一个事务去执行,新老事务是相互独立的,外部事务抛出异常,并不会影响内部事务的一个正常提交
nested 嵌套 如果当前存在事务,就嵌套在当前事务中去执行,如果当前没有事务,就新建一个事务
supports 支持 表示支持当前的事务,如果当前不存在事务,就以非事务的方式去执行
not supports 表示以非事务的方式来运行
如果当前存在事务,则需要把当前的事务挂起来
mandatory 强势 强势事务的执行,当前不存在事务就抛出一个异常
never 以非事务的方式来执行,如果当前存在事务则抛出异常
spring是一个轻量级的框架,提供了IOC和AOP这两个核心功能,想让开发者更关注业务的需求,不需要关心bean的一些管理,以及通过切面的方式对功能进行增强,从而去减少代码的侵入性
谈谈你对线程安全的理解线程安全表现有三个方面
1.原子性 2.有序性 3,可见性
原子性:一个线程执行一系列的的程序指令的操作的时候,他是不可中断的,一旦中断会出现前后数据不一致的结果
CPU的上下文切换是导致原子性的一个核心,而JVM提供一个synchronized关键字来解决
可见性:多线程环境下,读和写是发生在不同的线程里面的,有可能会出现某个线程对共享变量的修改,对其他线程不是实时可见的
导致原因有:CPU的高速缓存,CPU的指令重排徐,编译器的指令重排序
有序性:程序编写的指令顺序和CPU编写的指令顺序可能会出现不一致的现象,这种现象可以程之为指令重排序
可见性和有序性可以通过JVM提供的volatile这个关键字来解决
聚集索引就是基于主键创建的索引,除了主键以外的其他索引统一称为非聚集索引,也叫二级索引
聚集索引就是按照每张表的主键来构建这样一个B+树,然后叶子结点里面存储了这个表里面的每一行数据记录,所以基于Innodb这样的一个特征呢,聚集索引并不仅仅是一种索引类型,还代表了一种数据的存储方式.
同时意味着一张表里面必须要有一个主键,如果没有这个主键,Innodb会默认选择或添加一个隐藏列,作为主键索引来存储这个表的数据行
一般情况是建议使用自增ID作为主键,这样写入和检索性能都很高,如果是使用UUID这种随机id在频繁的插入数据的时候就会导致随机磁盘IO,从而会导致性能下降.
Inoondb里面只能存在一个聚集索引,由于主键索引是存储了一个表的完整数据,如果是基于非聚集索引来查询一条数据的时候,那么最终还是得访问主键索引来进行检索
阻塞队列呢是在队列的基础上增加了两个附加操作
第一个是在队列为空的时候获取元素的线程会等待队列变为非空
当队列满的时候,存储元素的线程会等待队列为可用
这样就可以非常容易去实现生产者与消费者这样的一个模型
如果队列满了,生产者就等待,队列空了,消费者也需要等待
用到两个关键的技术
1.队列元素的存储
2.线程阻塞和唤醒
ArrayBlockingQueue是基于数组结构的阻塞队列,队列的元素是存储在数组结构里面,并且由于数据的长度是有限制的,为了达到循环生产与消费的目的,ArrayBlockingQueue用到了一个循环数组,而线程的阻塞与唤醒用到了JUC里面的reentrantlock和condition,condition就相当于wait/notify在juc包里面的一个实现
threadlocal是一种线程隔离机制,他提供了多线程环境下对于共享变量访问的一个安全性,一般我们解决共享变量的情况是加锁,保证同一个时刻对一个共享变量进行更新,并且基于happes-bofore规则的一个锁监视器的一个规则,又能保证对数据修改之后对其他线程是可见的.
但是加锁呢会导致性能上的一个下降,所以threadlocal利用了一个空间换时间的思想,也就是说每个线程里面都有一个容器,来存储共享变量的一个副本,然后每个线程只对自己的变量副本做更新操作
具体时间原理是,在thread类里面有一个成员变量叫threadlocalmap,他专门用来存储当前线程的共享变量的一个副本,后续这个线程对共享变量的一个操作呢,都是去从threadlocalmap里面去进行变更的
wait让线程进入到阻塞状态
notify让阻塞的线程被唤醒
他们是成对出现的,从而去实现多个线程的通信
缓存雪崩就是存储在缓存里面的大量数据在同一个时刻全部过期,原理缓存里面的全部流量请求到了数据库,从而导致数据库压力增加,造成数据库服务器崩溃的现象
造成原因
1.缓存中间件宕机
对缓存中间件做高可用集群来避免
2.缓存中里面大部分key都设置了相同的过期时间,导致同一时刻这些key都过期了
可以在失效时间里面增加1-5分钟的随机值
缓存穿透是短时间内有大量不存在的key打到数据上,造成数据库的压力增加
我认为这是一个攻击行为
1.把无效的key保存到redis里面并设置一个特殊值,比如像null字符串.
2.使用布隆过滤器来实现
把目标数据全部缓存到布隆过滤器,当攻击者用不存在的key来请求的时候,先在布隆过滤器里面去进行查询,如果不存在,就意味着这个key肯定在数据库里面也不存在,所以这个时候也不会去访问数据库
3.访问数据库的层面去加锁
1.多线程下可以保证对共享变量的可见性
2.通过添加内存屏障防止多个指令之间的重排序
分布式锁是一种跨进程,跨机器节点的一种互斥锁
需要满足
排他性 只能有一个节点去访问共享资源
可重入性 他允许一个已经获得锁的线程在没有释放锁之前,重新去获得锁
锁的获取,释放
锁的失效机制 避免死锁的一个范围
比如redis 提供了setnx命令去实现锁的排他性,当key不存在的时候就返回1,存在就会返回0
然后还可以使用expire命令去设置锁的失效时间,从而去避免死锁.
redisson提供了分布式锁的封装实现,内置了一个watch dog机制,来对key做续期
死锁就是两个或者两个以上的线程,在执行的过程中去争夺同样一个共享资源,造成的相互等待的一个现象,如果没有外部的干预呢,线程会一直阻塞,无法往下去执行.这样一直处于相互等待资源的线程,我们称为死锁线程,
4个条件
1.互斥条件 共享资源X和Y只能被一个线程占用
2.请求和保持条件 线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X
3.不可抢占条件,其他线程不能强行抢占线程T1占有的资源
4.循环等待条件,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源
比如我们可以一次性申请所有的资源,这样的话就不存在锁要等待了
占用部分资源,在进一步申请其他资源的时候,如果申请不到,我们可以主动去释放他占用的资源
按序申请,先去申请资源序号小的,再去申请资源序号大的,
就是自动把第三方组件的bean装载到IOC容器里面,不需要开发人员自动写bean相关的一个配置
在spring应用类里面加上@springBootApplication注解就可以去实现自动装配
而真正实现自动装配的注解是@EnableAutoConfiguration
主要依靠三个关键的核心技术
1.引入starter,启动依赖组件的时候,要包含一个@configuration配置类,而在这个配置类里面,我们需要通过@Bean这个注解去声明需要装配到IOC容器里面的bean对象
2.这个配置类是放在第三方的jar包里面,然后通过springBoot中约定由于配置的这样一个理念,去吧这个配置类的全路径放在classpath:/meta-inf/spring.factories文件里面,这样springboot就能知道第三方jar配置类文件的位置,这个是用到了spring里面的springfactoriesLoader来完成的
3.springboot拿到第三方jar包里面声明的配置类以后,在通过spring提供的importselector这样一个接口来实现对这些配置类的动态加载,从而去完成自动装配这样一个动作
当业务层去尝试读取某个数据的时候会尝试去redis里面去加载,如果命中就直接返回,如果没有命中,就直接从数据库里面查询,查询到数据后在把数据缓存到redis里面
1.先更新数据库,在更新缓存
2.先删除缓存,在更新数据库
在多线程条件下可能仍然会有这样的问题,可以基于rocketMq的可靠性消息通信
失败的请求写入MQ事务消息,异步重试,确保成功
也可以通过canal组件监控mysql中的binlog的日志,把更新后的数据同步到redis里面
他是unsafe类的一个方法,他的全程是compareandswap 比较并交换
主要是为了保证多线程下,共享变量一个原子性
主要是atomic报里面的原子实现
第二个是实现多线程对共享资源竞争的互斥性质
1.concurrentHashMap整体架构
数组+链表+红黑色
默认会初始化一个长度等于16的数组
发生了哈希冲突会生成一条链表
当数组长度大于64并且链表长度大于等于8的时候
单向链表就会转化成红黑树,链表的长度小于8的时候,红黑树会退化成单向链表
2.concurrentHashMap的基本功能
在hashMap上提供了一个并发安全的实现
主要是通过对于node节点去加锁
3.concurrentHashMap在性能方面的优化
concurrentHashMap的锁的粒度是数组中的某一个节点,1.7是用的segment,锁的范围要更大,所以性能上他会更低.
引入红黑树这样的一个机制去降低了数据查询的时间复杂度.
数组的扩容,concurrentHashMap引入了多线程并发扩容的实现,多个线程对原始数据分片,每个线程去负责一个分片的数据迁移.
从而提升了扩容过程中数据迁移的一个效率
concurrentHashMap的size()方法来获取元素的个数,而在多线程并发环境中保证原子性的条件下去实现元素个数的累加,性能是非常低的
concurrentHashMap做了2个优化
concurrentHashMap在线程竞争不激烈的时候直接采用了CAS的方式,来实现原子性的递增
如果线程竞争比较激烈的情况下,直接使用一个数组来维护元素个数,如果要增加元素个数直接从数组中随机取一个,在通过CAS算法来实现原子递增,核心思想是引入数组来实现对并发更新的一个负载
哈希冲突是由于哈希算法被计算的数据是无限的,而计算后的结果的范围是有限的,所以总会存在不同的数据,经过计算之后得到的值是一样,这个情况下就会出现所谓的哈希冲突
解决哈希冲突的方法有4种
1.开放定址法 从冲突位置的那个位置开始,从hash表中去找到一个空闲的位置,然后把发生冲突的元素存入到这个位置 threadlocal就用到了
2.链式寻址法 把存在hash冲突的key以单向链表的方式进行存储 hashMap用到了此方法
3.在hash法,再次用hash函数进行运算
4.建立公共溢出区 把hash表分为基本表和溢出表两个部分 凡是存在冲突的元素放在溢出表中
引入contDownLatch这样一个计数器
它可以通过初始化指定的一个计数器去进行倒计时,其中他提供了两个方法,分别是await()阻塞线程,以及countDown()去进行倒计时,一旦倒计时归0,所有被阻塞在await()方法的线程都会被释放.
1.lock和synchronized都是java中解决线程安全的一个工具
2.synchronized是java中的同步关键字
lock是JUC包里面提供的一个接口
synchronized 可以修饰在方法上面,修饰在代码块上,锁的对象是静态对象或者是类对象,就属于全局锁.如果是普通实例对象,锁的范围取决于实例的生命周期.
lock中锁的粒度是他提供的lock方法和unlock方法来决定的,
lock比synchronized灵活度更高,可以自主的决定上面时候加锁和释放锁,只需要调用lock和unlock这两个方法就可以了
同时lock还提供了非阻塞竞争锁的方法trylock(),这个方法可以通过返回true/false来告诉当前线程是否有其他线程正在使用锁了.
lock提供了公平和非公平锁的机制
公平锁是指线程竞争锁资源的时候,如果已经有其他线程正在排队,或者等待锁释放,那么当前竞争锁的线程是无法去插队的,而非公平锁不管是否有线程在排队等待锁,他都会去尝试去竞争一次锁.synchronized只提供了非公平的实现
3.lock是自旋锁实现性能优化 两者性能都差不多



