文章目录
1. 并发控制,synchronize 和 lock的区别
在分布式开发中,锁是线程控制的重要途径,synchronize、lock即Java中两种重要的线程控制机制,其主要区别在于:
类别 | synchronized | lock |
---|---|---|
存在层次 | Java关键字,在jvm层面上,通过字节指令控制程序 | 一个类(接口) |
锁的释放 | 1. 获取锁的线程执行完同步代码,释放锁;2. 线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入、不可中断、非公平 | 可重入、可中断、可公平可不公平 |
性能 | 少量同步 | 大量同步 |
小结 ⇒ 与Synchronized相比ReentrantLock的优势:
- 支持获取锁的超时时间
- 获取锁时可被中断
- 可以设为公平锁
- 可以有不同的条件变量,即有多个waitSet,可指定唤醒
另:jdk1.6以后对synchronized做了相应的优化,包括:线程自旋和自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁等。
2. 多线程的状态流转、等待和阻塞的区别
2.1 线程在操作系统层面分为五种状态
- 初始状态:创建线程对象时状态
- 可运行状态(就绪状态):调用start()方法后进入就绪状态,即准备还被CPU调度执行
- 运行状态:线程获取CPU的时间片,执行run()方法的逻辑
- 阻塞状态:线程被阻塞,放弃CPU的时间片,等待解除阻塞重新回到就绪状态竞争时间片
- 终止状态:线程执行完成或抛出已成后的状态
2.2 线程在 Java API层面分为六种状态
- NEW,线程对象被创建
- Runnable,线程调用start()方法后进入该状态,该状态包含三种情况:(1)就绪状态,等待CPU分配时间片;(2)运行状态,进入Runnable方法执行任务(3)阻塞状态,BIO执行阻塞式IO流时的状态
- Blocked,没获取到锁时的阻塞状态
- Waiting,调用wait()、join()等方法后的状态
- TIMED_WAITING,调用sleep(time)、wait(time)、join(time)等方法后的状态
- TERMINATED,线程执行完成或抛出异常后的状态
2.3 Wait 和 Sleep 的区别
wait和sleep都可以让线程进入阻塞状态,其区别主要在于:
- wait是Object的方法,sleep是Thread的方法
- wait会立即释放锁,sleep不会释放锁
- wait后线程的状态是Waiting;sleep后线程的状态是Time_Waiting
线程阻塞方式:
- BIO阻塞,即使用了阻塞式的IO流
- sleep(long time)线程休眠进入阻塞状态
- 调用a.join()方法的线程进入阻塞,等待a线程执行完恢复运行
- synchronized或reentrantLock造成线程未获得锁进入阻塞状态
- 获得锁后调用wait()方法,会让线程进入阻塞状态
- LockSupport.park()会让线程进入阻塞状态
3. zookeeper 角色
3.1 认识zookeeper
zookeeper时一款开源的分布式协调服务框架,为分布式环境提供了一致性服务的功能,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作,最终将简单易用的接口和性能高效、功能稳定的系统提供给用户。
常见应用场景:发布订阅、主动通知、文件管理、集群管理、分布式锁等功能。zookeeper在设计的时候满足了CP两要素,即一致性和分区容错性。
3.2 Zookeeper的工作原理
- 每个Server在内存中存储了一份数据;
- Zookeeper启动时,将从实例中选举一个leader(Paxos协议)
- Leader负责处理数据更新等操作
- 一个更新操作成功,当且仅当大多数Server在内存中成功修改数据。
3.3 zookeeper角色
- 领导者(leader),负责进行投票的发起和决议,更新系统状态
- 学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票
- 观察者(Observer),可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度
- 客户端(client),请求发起方
4. 分布式锁的原理
4.1为什么使用分布式锁
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
在传统单机应用单机部署的情况下,可以使用java并发处理的相关API进行互斥控制(如ReentrantLock或Synchronized)
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的java API并不能提供分布式锁的能力。
为了解决这个问题,就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
4.2 分布式锁的特性
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的的一个线程执行;
- 高可用的获取锁与释放锁;
- 高性能的获取锁与释放锁;
- 具备可重入特性;
- 具备锁失效机制,防止死锁;
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
4.3 分布式锁的实现方式
分布式的CAP理论告诉我们,任何一个分布式系统都无法同时满足一致性、可用性、和分区容错性,最多只能同时满足两项。
所以,很多系统在设计之初就对这三项做了取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性,只要这个最终时间实在用户可以接受的范围内即可。
在很多场景中,我们为例保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。
- 基于数据库实现方式
实现思想:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。 - 基于redis实现方式
实现思想:step1,获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断;step2,获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁;step3,释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放 - 基于ZooKeeper实现方式
实现思想:ZooKeeper是为分布式应用提供一致性服务的开源组件,内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名
参考链接:分布式锁简单入门以及三种实现方式介绍
5. redis的分布式方式
6. redis存储的内部数据结构
7. redis通过什么方式实现主从复制
8. MySQL为什么选择B+树作为索引结构,不选择B树
8.1 B树、B-树、B+树
B树即二叉搜索树
- 所有非叶子结点至少拥有两个儿子(Left和Right)
- 所有结点存储一个关键字
- 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树
B-树,一种多路搜索树
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点
- 定义任意非叶子结点最多只有M个儿子;且M>2;
- 根结点的儿子数为[2, M];
- 除根结点以外的非叶子结点的儿子数为[M/2, M];
- 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
- 非叶子结点的关键字个数=指向儿子的指针个数-1;
- 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
- 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
- 所有叶子结点位于同一层
例如:M=3
B+树是B-树的变体,也是一种多路搜索树:
- 其定义基本与B-树同,除了:
- 非叶子结点的子树指针与关键字个数相同;
- 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
- 为所有叶子结点增加一个链指针;
- 所有关键字都在叶子结点出现;
例如:M=3
8.2 为什么使用B+树
- 如果是哈希结构,也就是key-value一对一关系,用于等值查询尚可,但是范围查询便无能为力了。
- 如果是二叉树,如果二叉树特殊化为一个链表,相当于全表扫描,因此二叉树不适合索引结构。
- 如果是平衡二叉树(也是一棵二叉搜索树,任何节点的两个子树高度最大差为1),虽然不会出现“特殊化的链表”情况。但是,平衡二叉树在插入或更新时,需要左旋右旋维持平衡,代价大,并且数量多的话,树的高度会很高,因数据存储在磁盘,以它作为索引结构,每次从磁盘读取一个节点,操作IO的次数过多。
- 如果是B+树
B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。
B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。