java 锁
什么是锁
- 在单进程的进程中,当多个线程可以同时改变某个单独存在的共享变量时,需要对变量或者代码块进行同步,从而在修改变量是能够灵活的执行删除变量的同时修改
- 为了实现多个个体在同一时间点,代码块一个线程可以实现,需要在特定的地方做一个标记,标记方式才能看到,发现已经有标记了,那么即将到来标记的线程同步代码块取消之间可以结束实验标记,这个标记为锁
- 不同的地方实现锁的方式也不一样,只要能看到所有线程都可以得到满足,如java中同步时在对象头标记,锁接口的实现类实际上是在一个动态类型变量,其日常快速生命都可以知道的,内核中也是利用相互排斥量或者信号量等数据标记
- 利用内存数据做锁柜,实际上互不互据的做锁,如结合做水中流水号和时间结合可以做锁,或者是一个不会释放只需要满足在对象标记进行修改,才能保证原子性和内存可见性
- 什么是锁,千万不要被学的内容限制了锁的概念,只要能够完成锁功能的任何事物都可以称为锁,思想要放开
为什么会用到锁
- 一个分布式系统不可能同时满足任何一个关系(整体),可用性(availability)和局部容错性(partition tolerance),最多只能满足整个环境
- 现在很多的大型网站大多应用了故事情节中的故事,一直是一个重要的故事,在互联网领域的需要的场景中,需要有互联网的人来系统的高可用性,系统往往只需要最终的结果
布景
- 主要之多个服务模式下,同时有多个服务开启
- 在许多场景中,我们是为了保证数据的最终结果,需要很多的技术方案来支持,比如说用假锁等,但是很多情况并没有那么简单:
- 与单机最大的不同的不仅仅是多线程,还有各个服务之间的异步
- 线程因为可以共享堆内存,所以可以简单的移动内存作为标记存储位置
- 我扩充一点,在线程之下,还有协程,协程比线程还要小,协程是属于线程的,类似于线程属于协程
什么是嵌入式锁
- 当在模拟模型下,数据只有一个(或者有限制),此时需要利用锁的控制时刻的过程数据
- 与单机模式下的锁需要保证进程可见,还需要考虑进程与锁之间的网络问题
- 有一个很重要的问题,有网络之后我们要考虑到网络的时延等问题
- 应用锁还是可以将标记内存,只是将内存不是某个内存在进程分配的公共内存,如redis,memcache。至于利用数据库,等做锁与单机的实现是一样的,只要保证标记能互通排斥就可以
什么样的锁符合需求
- 可以支持分布式,能够在同一时间被同一台机器上的一个线程执行
- 是可重入的,为了避免死锁
- 锁最好是一把锁,可以适应各种场景
- 是一把公平的锁,可以根据场景考虑用不用
- 有很好的获取锁和释放锁的功能
- 获取锁和释放锁的性能很好
基于数据库的应用锁
基于数据库,如MySQL
-
基于数据库表
- 要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了
- 当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录
我们对特定记录做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成
-
存在问题
- 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用
- 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁
- 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作
- 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了
-
基于数据库排他锁
- 基于MySql的InnoDB引擎
- 查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁
- 可以通过connection.commit()操作来释放锁
public boolean lock(){
connection.setAutoCommit(false)
while(true){
try{
result = select * from methodLock where method_name=xxx for update;
if(result==null){
return true;
}
}catch(Exception e){
}
sleep(1000);
}
return false;
}
- 总结
- 这两种方式都是依赖数据库的一张表
- 一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁
- 数据库实现分布式锁的优点:直接借助数据库,容易理解
- 数据库实现分布式锁的缺点:会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂
- 操作数据库需要一定的开销,性能问题需要考虑
- 使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候
- 分布式效果最差的实现方式,不建议使用
基于缓存,如Redis
- setnx()
- setnx()的含义就是SET if NOT EXISTS,其主要有两个参数setnx(key,value)。该方法是原子的,如果key不存在,则设置当前key成功,返回1,如果key存在,则设置当前key失败,返回0
- expire()
- expire设置过期时间,要注意设置x不能设置key的超过时间,通过expire()来对key进行设置
- 我来通俗的解释下,setnx函数是用来设置锁谁使用的,expire函数设置时间,一个执行,一个控制
- 使用步骤
- setnx(localkey,1)如果返回0,则说明占位失败,如果返回1,则说明占位成功
- expire设置过期时间,防止死锁问题
- 执行业务后,可以通过删除命令删除key
基于Zookeeper、etcd等
- zookeeper相关基础知识
- ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务
- 是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件
- 它是一个典型的分布式数据一致性解决方案
- 分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅,负载均衡,命名服务,分布式协调/通知,集群管理,Master选举,分布式锁和分布式队列等功能
- 主要是用来解决分布式应用中经常遇到的一些数据管理问题
- zookeeper锁相关基础知识
- ZooKeeper的每一个节点,都是一个天然的顺序发号器
- ZooKeeper节点的递增有序性,可以确保锁的公平
- ZooKeeper的节点监听机制,可以保障占有锁的传递有序而且高效
- ZooKeeper的节点监听机制,能避免羊群效应
- 实现步骤
- 在lock节点下创建一个临时节点
- 判断创造的新生顺序是否最小,是最小的则获得成功,不是则获取失败,然后看顺序比前一个阶段
- 当获取锁失败,设置手表后则手表事件发生后,开始判断是否顺序小
- 取锁成功则执行代码,最后释放锁(删除该节点)
- zookeeper锁优缺点
- 优点:ZooKeeper分布式锁(如InterProcessMutex),能有效的解决分布式问题,不可重入问题,使用起来也较为简单
- 缺点:ZooKeeper实现的分布式锁,性能并不太高
韩清宗