一、常见方式

1.1 synchronized 加锁【只能解决单个jvm线程安全问题】
1.2 Set化后的MQ

我们可以按用户ID做Set(用户ID % Set数)进而分成多个组,为不同的组创建不同的MQ队列,这样一个用户同一时间只在一个队列中,一个队列的处理是串行化的,实现了锁的功能,同时又有多个Set来完成并行化,在性能上会好于分布式锁,并且代码上没有太多改动

1.3 乐观锁

传入版本号,每次更新时,版本号加1 ,更新的条件是版本号要等于传入的版本号

1.4 关系型数据库
1.5 redis锁
  • 原理
    使用redis实现分布式锁时,加锁操作必须是原子操作,否则多客户端并发操作时会导致各种各样的问题。由于我们实现的是可重入锁,加锁过程中需要判断客户端ID的正确与否。而redis原生的简单接口没法保证一系列逻辑的原子性执行,因此采用了lua脚本来实现加锁操作。lua脚本可以让redis在执行时将一连串的操作以原子化的方式执行。
  • 应用
    基于注解的redis锁
1.6 Zookeeper 锁

redission多redissclient联锁 redis并发锁_java

  • 原理
    使用zookeeper创建临时序列节点来实现分布式锁,适用于顺序执行的程序,大体思路就是创建临时序列节点,找出最小的序列节点,获取分布式锁,程序执行完成之后此序列节点消失,通过watch来监控节点的变化,从剩下的节点的找到最小的序列节点,获取分布式锁,执行相应处理,依次类推……
  • 步骤

1)多个Jvm同时在Zookeeper上创建同一个相同的节点( /Lock)
2)zk节点唯一的! 不能重复!节点类型为临时节点, jvm1创建成功时候,jvm2和jvm3创建节点时候会报错,该节点已经存在。这时候 jvm2和jvm3进行等待。
3)

  • PS
    如果程序一直不处理完,可能导致死锁(其他的一直等待)。设置有效期~ 直接close()掉 其实连接也是有有效期设置的

二、细节

2.1 锁释放与超时

我们必须要加上超时时间,如(tryLock(<等待锁的时长>,<锁占用的最大时长>)),超时时间设置过长会导致服务异常后无法及时获取新的锁,过短又有可能在业务没有执行完锁提前释放了。更优雅但偏复杂的方法是使用心跳超时设置,即与占有锁的服务保持心跳,在心跳超时后再释放锁

2.2 性能及高可用

出于性能考虑,一般分布式锁都是非公平锁,如果要保证加锁顺序二选用公平锁时要注意对性能的影响。加解锁操作本身要保证性能及可用性,避免单点,锁信息要持久化,慎用自旋避免浪费CPU

2.3 数据一致性

数据一致性 分布式锁要合理设置锁标记以区分是哪个实例、哪个线程的操作,可重入锁要做好计数及相应的unlock次数,同时必须保证数据的一致,这要求我们只能选择CP特性(见下文)的服务作为分布式锁的中间件

三、要点

3.1 如何模拟分布式锁场景
package com.toov5.Lock;

public class OrderService implements Runnable {

    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator(); // 定义成全局的
    private ExtLock lock = new ZookeeperDistrbuteLock();

    public void run() {
        getNumber();
    }

    public synchronized void getNumber() { // 加锁 保证线程安全问题 让一个线程操作
        try {
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",number" + number);

        } catch (Exception e) {

        } finally {
            lock.unLock();
        }
    }

    public static void main(String[] args) {
//        OrderService orderService = new OrderService();
        for (int i = 0; i < 100; i++) { // 开启100个线程
            //模拟分布式锁的场景
            new Thread(new OrderService()).start();
        }
    }

}
3.2 可靠性分布式系统具备特性
  • 互斥性
    作为锁,需要保证任何时刻只能有一个客户端(用户)持有锁
  • 可重入
    同一个客户端在获得锁后,可以再次进行加锁
  • 高可用
    获取锁和释放锁的效率较高,不会出现单点故障
  • 自动重试机制
    当客户端加锁失败时,能够提供一种机制让客户端自动重试
3.3 redis 基本操作

添加key:
SET foo bar EX 20 NX
GET foo

四、常见问题

4.1 获取redis 锁失败