线程锁

给方法、代码块加锁,某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如Synchronized、Lock等

进程锁

控制同一个系统中多个线程访问同一个共享资源,因为程序的独立性,各个进程无法控制其他进程对资源的访问,可以利用操作系统的信号量机制

分布式锁

多个进程不在同一个系统中,使用分布式锁控制多个进程对同一资源的访问

特性
  • 互斥性:任意时刻,只能有一个客户端可以获取锁
  • 安全性:: 锁只能被持有该锁的客户端删除,不能由其它客户端删除
  • 死锁: 获取锁的客户端因为某些原因(如down机等)而未能释放锁,其它客户端再也无法获取到该锁。
  • 容错:当部分节点(redis节点等)down机时,客户端仍然能够获取锁和释放锁
实现分布式锁的三种方式
  • 基于缓存(Redis等)实现分布式锁;
  • 基于数据库实现分布式锁;
  • 基于Zookeeper实现分布式锁;
基于Redis的分布式锁
利用SETNX和SETEX

使用key-value的方式,以数据的某个属性作为键,给改代码块或者对象加锁。在进程初次使用改数据时,会先检查键是不是已经被创建(被锁),否则创建键(加锁)。发现数据被锁后,会不断的在设置超时时间的范围内循环请求该数据,直到超出超时时间。

缺点:

  1. 高并发的情况下,如果两个线程同时进入循环,可能导致加锁失败。
  2. SETNX 是一个耗时操作,因为它需要判断 Key 是否存在,因为会存在性能问题。

官方推荐使用Redlock实现分布式锁,更加可靠;

基于数据库表的分布式锁
乐观锁

乐观锁认为不会有人同时修改数据,即不会上锁,只有在最终更新数据的时候判断一下再次期间别人有无修改数据,如果有修改,则放弃操作,否则执行操作。

实现原理:

一般通过version来实现,也就是在数据库表创建一个 version 字段,每次更新成功,则 version+1,读取数据时,我们将 version 字段一并读出,每次更新时将会对版本号进行比较,如果一致则执行此操作,否则更新失败!

悲观锁

基于InnoDB引擎,使用数据库的排它锁实现,在操作数据时直接把数据锁住,直到操作完成再释放锁,上锁期间其他人不能修改数据。

排它锁原理:

for update是一种行级锁,又叫排它锁。一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行。行锁永远是独占方式锁。只有当出现如下的条件时,才会释放锁:1、执行提交(COMMIT)语句;2、退出数据库(LOG OFF);3、程序停止运行。

实现原理:

创建一个数据表用于记录分布式锁(可以区分业务模块),然后在需要使用分布式锁的地方,通过select……for update获取对应业务模块的锁记录,如果获取成功,该记录行被锁定,其他线程将只能等待,当该线程执行结束后,就会释放锁,其他线程就可以获取锁并继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

通过以下方法解锁:

1
2
3
public void unlock(){
connection.commit();
}
基于Zookeeper实现分布式锁

实现原理为:

  1. 建立一个节点,假如名为 lock 。节点类型为持久节点(Persistent)
  2. 每当进程需要访问共享资源时,会调用分布式锁的 lock() 或 tryLock() 方法获得锁,这个时候会在第一步创建的 lock 节点下建立相应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL),通过组成特定的名字 name+lock+顺序号。
  3. 在建立子节点后,对 lock 下面的所有以 name 开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,假如是最小节点,则获得该锁对资源进行访问。
  4. 假如不是该节点,就获得该节点的上一顺序节点,并监测该节点是否存在注册监听事件(上一个节点是不是被删除)。同时在这里阻塞。等待监听事件的发生,获得锁控制权。前一个Znode删除的时候,会触发Znode事件,当前节点能监听到删除事件,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后,只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,自己就获得锁。
  5. 当调用完共享资源后,调用 unlock() 方法,关闭 ZooKeeper,进而可以引发监听事件,释放该锁。