JavaEE:分布式锁使用

一、非分布式环境的锁使用:

说明:只能在非集群Web应用(单JVM环境)中使用。

1.update行锁,在...Mapper.xml中:

<mapper ...>
   <update id="方法名">
      update 表名 set ...,   #行锁,update语句执行时,其他操作等待
      where 条件
   </update>
</mapper>

2.synchronized锁:

(1)说明:

特性:互斥性、可重入性、非公平性,由JVM实现(自动加锁/解锁),与ReentrantLock性能类似。

Mark Word:

状态               56bit                  1bit   4bit       1bit偏向锁标志    2bit锁标志
无锁         1-25为空, 31位HashCode        空     对象年龄     0                01
偏向锁       54位线程ID, 2位epoch           空     对象年龄    1                 01
轻量级锁     62位指针(指向轻量级锁记录)                                          00
重量级锁     62位指针(指向重量级锁记录)                                          10
GC标记       空                                                               11

偏向锁:加/解锁耗时少,多线程夺锁时有锁撤销操作,所以适用单线程的业务场景
轻量级锁:锁占用时间短,多线程夺锁不阻塞,夺不到锁时会自适应自旋浪费CPU资源,所以适用耗时少的业务场景
重量级锁:多线程夺锁不会浪费CPU资源,夺不到锁时会阻塞,所以适用耗时长的业务场景

锁升级流程:

检查锁状态 -> 1.偏向锁、无锁(无锁时跳第3步) -> 2检查线程ID指向A线程(是时执行同步代码,结束流程) -> 3 CAS替换线程ID(成功时执行同步代码,结束流程) -> 4撤销A线程偏向锁 -> 5暂停其他线程并检查状态(非活动状态时,释放其他线程,唤醒A线程,A线程跳第3步) -> 6 A线程升到轻量级锁 -> 6.5 CAS操作(成功时执行同步代码,结束流程) -> 7 A线程自适应自旋(获得轻量锁时执行同步代码,结束流程) -> 8 A线程升到重量级锁 -> 9 A线程阻塞等待其他线程释放锁

(2)使用:

synchronized方法锁:

@Autowired
PlatformTransactionManager platformTransactionManager;
@Autowired TransactionDefinition transactionDefinition;
public String synchronized createServiceOrder(){ //1.方法添加synchronized
   //2.获取事务
   TransactionStatus t = platformTransactionManager.getTransaction(transactionDefinition);
   ...   //查询医院与医生信息
   if(判断当前医院与医生是否存在){//3.1不存在时回滚
      platformTransactionManager.rollback(t);
      throw new Exception("选择的医院或医生不存在");
   }
   if(判断当前用户剩余服务次数){//3.2次数为0时回滚
      platformTransactionManager.rollback(t);
      throw new Exception("选择的医院或医生不存在");
   }
   ...   //更新数据:减少用户的服务次数
   //4.提交事务
   platformTransactionManager.commit(t);
   ...   //5.生成订单
   return 订单id;
}

synchronized块锁:

@Autowired
PlatformTransactionManager platformTransactionManager;
@Autowired TransactionDefinition transactionDefinition;
public String createServiceOrder(){
   synchronized(){ //1.添加synchronized
      //2.获取事务
      TransactionStatus t = platformTransactionManager.getTransaction(transactionDefinition);
       ...   //查询医院与医生信息
      if(判断当前医院与医生是否存在){//3.1不存在时回滚
         platformTransactionManager.rollback(t);
         throw new Exception("选择的医院或医生不存在");
      }
      if(判断当前用户剩余服务次数){//3.2次数为0时回滚
         platformTransactionManager.rollback(t);
         throw new Exception("选择的医院或医生不存在");
      }
      ...   //更新数据:减少用户的服务次数
      //4.提交事务
      platformTransactionManager.commit(t);
   }
   ...   //5.生成订单
   return 订单id;
}

3.ReentrantLock锁(可重入锁):

特性:互斥性、可重入性、非公平(性能高)/公平性,由JDK实现(手动加锁/解锁),与synchronized性能类似。

(1)ReentrantLock使用:

Lock lock = new ReentrantLock();       //非公平性
//Lock lock = new ReentrantLock(true); //公平性
@Autowired
PlatformTransactionManager platformTransactionManager;
@Autowired TransactionDefinition transactionDefinition;
public String createServiceOrder(String id){
   //1.获取锁
   lock.lock();
   //lock.tryLock();             //尝试获得锁,失败返回false
   //lock.tryLock(时间, 单位);   //同上,指定超时时间
   //lock.lockInterruptibly();   //获得锁,线程Interrupt时抛出异常
   try {
      //2.获取事务
      TransactionStatus t = platformTransactionManager.getTransaction(transactionDefinition);
      ...   //查询医院与医生信息
      if(判断当前医院与医生是否存在){//3.1不存在时回滚
         platformTransactionManager.rollback(t);
         throw new Exception("选择的医院或医生不存在");
      }
      if(判断当前用户剩余服务次数){//3.2次数为0时回滚
         platformTransactionManager.rollback(t);
         throw new Exception("选择的医院或医生不存在");
      }
      ...   //更新数据:减少用户的服务次数
      //4.提交事务
      platformTransactionManager.commit(t);
   } finally {
      //5.释放锁
      lock.unlock();
   }
   ...   //6.生成订单
   return 订单id;
}

(2)Condition暂停/唤醒线程(写在lock - unlock中间):

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();  //获得Condition

public void wait1(){
   try {
      lock.lock();
      //...
      condition.await();  //暂停线程,写在lock - unlock中间,等同Object.wait()
   } finally {
      lock.unlock();
   }
}
public void notify1(){
   try {
      lock.lock();
      //...
      condition.signal();       //唤醒线程,写在lock - unlock中间,等同Object.notify()
      //condition.signalAll();  //唤醒线程,写在lock - unlock中间,等同Object.notifyAll()
   } finally {
      lock.unlock();
   }
}

4.ReentrantReadWriteLock锁:

特性:线程间读锁共享/写锁互斥性、可重入性、非公平(性能高)/公平性、锁降级(从写锁降级为读锁),由JDK实现(手动加锁/解锁),适用读多写少的场景。

获取读锁:获取读锁时,代码块不能有其他线程获得写锁。
获取写锁:代码块不能有其他线程的读锁与写锁。

(1)使用:

lock.readLock().lock();        //1.获取读锁
if (true) { //判断缓存中数据是否失效,失效进入if内部
    //1.1释放读锁,获得写锁,更新缓存数据
    lock.readLock().unlock();
    lock.writeLock().lock();
    //...1.2向缓存中写新数据
    //1.3获取读锁,释放写锁 -> 锁降级
    lock.readLock().lock();
    lock.writeLock().unlock();
}
//...从缓存中获取数据
lock.readLock().unlock();      //2.释放读锁

5.StampedLock锁(ReentrantReadWriteLock加强版):

特性:不可重入、Writing/Reading/Optimistic reading三种模式、允许读锁/写锁互转、获得锁时next=0表示失败,也适用读多写少的场景。

Writing模式:类似ReentrantReadWriteLock写锁
Reading模式(悲观读):类似ReentrantReadWriteLock读锁,读过程中不允许写操作
Optimistic reading模式(乐观读):读过程中允许写操作

(1)写锁使用:

private void insert() {
    long next = 0;
    try {
        next = lock.writeLock(); //获得写锁
        //...写业务
    } finally {
        lock.unlockWrite(next);  //释放写锁
    }
}

(2)乐观读锁+悲观读锁使用:

private String query(){
    long next = lock.tryOptimisticRead(); //1.1 获得乐观读锁
    if(true){ //验证是否有写操作,有进入if
        try {
            next = lock.readLock();  //1.2 获得悲观读锁,重新读数据
            //...读业务
        } finally {
            lock.unlockRead(next);  //2 释放悲观读锁
        }
    }
    return "...";
}

(3)悲观读锁+写锁使用:

private String queryAndInsert() {
    long next = lock.readLock(); //1.1 获得悲观读锁
    //...读业务
    try {
        long tempNext = lock.tryConvertToWriteLock(next);  //1.2 将悲观读锁转为写锁
        if (tempNext != 0) {
            next = tempNext; //获得写锁成功
        } else { //1.3切换写锁失败时,关闭悲观读锁,获取写锁
            lock.unlockRead(next);
            next = lock.writeLock();
        }
        //...写业务
    } finally {
        lock.unlock(next);  //2 释放锁
    }
    return "...";
}

二、分布式环境的锁使用:

说明:能在集群Web应用(多JVM环境)中使用。

1.数据库悲观锁(对数据库压力大):

格式(操作同一条数据,使用for update锁定,其他线/进程进入等待):

select * from 表名 where 条件 for update;

mapper映射文件:

UserMapper.xml内容(sql语句后加for update):
<mapper ...>
   <select id="queryUser" resultType="com.yyh.domain.User">
      select * from user 
      where user_id = #{userId,jdbcType=VARCHAR} 
      for update
   </select>
</mapper>

Dao类:

public class UserMapper {
   ...
   User queryUser(@Param("userId")String userId);
}

Service类:

@Resource
UserMapper userMapper;

@Transactional(rollbackFor = Exception.class)   //对方法加事务
public User getUser(String userId){
   return userMapper.queryUser(userId);
}

2.Redis分布式锁(不支持阻塞):

说明:

随机值:用于自动释放锁时校验
NX:原子性,利用此值获取锁,键存在时不设值,键不存在时设值并获得锁
PX:过期时间,单位为毫秒,过期自动释放锁
SET 键名 随机值 NX PX 毫秒值

(1)获取/释放Redis锁:

public class RedisDisLock implements AutoCloseable {  //继承
   RedisTemplate redisTemplate;
   String key;  //键名
   String randomValue;  //随机值

   public RedisDisLock(RedisTemplate redisTemplate, String key){
      this.redisTemplate = redisTemplate;
      this.key = key; //键名
      this.randomValue = UUID.randomUUID().toString();  //随机值
   }

   //获得Redis锁
   public boolean getRedisLock(){
      RedisCallback<Boolean> callback = connection -> {
         byte[] keyB = redisTemplate.getKeySerializer().serialize(key);  //将键名转为byte[]
         byte[] randomValueB = redisTemplate.getKeySerializer().serialize(randomValue);  //将随机值转为byte[]
         Expiration expiration = Expiration.seconds(60); //过期时间
         RedisStringCommands.SetOption nx = RedisStringCommands.SetOption.ifAbsent();          //NX
         return connection.set(keyB, randomValueB, expiration, nx);   
      }
      return redisTemplate.execute(callback);  获取Redis锁
   }

   //释放锁(Lua脚本):
   public boolean unRedisLock(){
        RedisScript<Boolean> script = RedisScript.of(
          "if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n" +   //校验锁
          "   return redis.call(\"del\", KEYS[1])\n" +            //释放锁
          "else\n" +
          "   return 0\n" +
          "end", 
        Boolean.class);
        return (Boolean) redisTemplate.execute(script, Arrays.asList(key), randomValue);  //释放Redis锁
      }
   }

   //自动释放锁
   @Override
   public void close() throws Exception {
      unRedisLock(); //释放锁
   }
}

(2)使用Redis锁:

@Autowired
RedisTemplate redisTemplate;

public void test(){
   try (RedisDisLock lock = new RedisDisLock(redisTemplate, "自定义键名")) {
      if (lock.getRedisLock()){  //获取锁,true表示有锁,false为没有锁
         //此处执行锁内具体逻辑
      }
   } catch (Exception e) {
      e.printStackTrace();
   }
}

3.Redisson分布式锁(基于Redis实现,支持阻塞):

官方文档:

https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95

(1)使用Java代码配置方式,获取Redisson锁:

<1>导入Redisson依赖包,在pom.xml中:

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.11.2</version>
</dependency>

<2>单点Redis:

public void test(){
   Config config = new Config();
   config.setTransportMode(TransportMode.EPOLL);
   config.useSingleServer()   //单点Redis
         .addNodeAddress("redis://192.168.133.141:7181");  //可以用"rediss://"来启用SSL连接
   RedissonClient redisson = Redisson.create(config);
   RLock lock = redisson.getLock("自定义键名");
   try{
      lock.lock(30, TimeUnit.SECONDS);  //获得锁
      //此处执行锁内具体逻辑
   } catch (Exception e){
      e.printStackTrace();
   } finally {
      try{
         lock.unlock();  //释放锁
      } catch (Exception e){
         e.printStackTrace();
      }
   }
}

<3>集群Redis:

public void test(){
   Config config = new Config();
   config.useClusterServers()  //集群Redis
       .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
       .addNodeAddress("redis://192.168.233.133:6379", "redis://192.168.233.134:6379") 
       .addNodeAddress("redis://192.168.233.135:6379")  //可以用"rediss://"来启用SSL连接
   RedissonClient redisson = Redisson.create(config);
   //后面代码同上单点Redis方式
}

(2)使用SpringBoot配置方式,获取Redisson锁:

<1>导入依赖:

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.11.2</version>
</dependency>

<2>application.yml文件中配置redis:

spring:
  redis:
    database: 0
    host: 192.168.233.133  #redis服务器IP
    port: 6379  #redis服务器端口
    ...   #其他redis的配置

<3>代码:

@Autowired
RedissonClient redisson;  //自动装载
public void test(){
   RLock lock = redisson.getLock("自定义键名");
   try{
      lock.lock(30, TimeUnit.SECONDS);  //获得锁
      //此处执行锁内具体逻辑
   } catch (Exception e){
      e.printStackTrace();
   } finally {
      try{
         lock.unlock();  //释放锁
      } catch (Exception e){
         e.printStackTrace();
      }
   }
}

4.Zookeeper分布式锁(支持阻塞):

(1)进入Zookeeper命令行:

#查看节点列表
ls /
#创建分布式锁节点
create /lock dis-lock
#获取指定节点数据
get /lock

(2)获取/释放ZooKeeper锁:

public class ZooKeeperLock implements AutoCloseable, Watcher {
   ZooKeeper zooKeeper;
   String tempNode;
   public ZooKeeperLock(){
      //第1个参数为ZooKeeper服务器的IP+端口,第2个参数为超时时间
      this.zooKeeper = new ZooKeeper("192.168.233.141:2181", 10000, this);
   }
   public boolean getZKLock(String name){ //获取锁,name为业务名称(自定义)
      try{
         Stat s = zookeeper.exists("/" + name, false); //判断根节点是否存在
         if(s == null){ //根节点不存在时
            zooKeeper.create("/" + name, name.getBytes() ZooDefs.lds.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  //1.创建根节点
         }
         tempNode = zooKeeper.create("/" + name + "/" + name + "_", name.getBytes() ZooDefs.lds.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //2.创建瞬时有序节点
         List<String> childNodeList = zooKeeper.getChildren("/" + name, false);  //获取子节点列表
         Collections.sort(childNodeList);                                        //对子节点列表排序
         if(tempNode.endsWith(childNodeList.get(0))) return true;  //判断创建的节点是否是第1个,是获得锁,不是进入下面监听前一个节点
         /*
         3.监听上一个节点
         */
         String preNode = childNodeList.get(0);
         for (String itemNode: childNodeList) {
            if(tempNode.endsWith(itemNode)){
               zookeeper.exists("/" + name + "/" + preNode, true);
               break;
            }
            preNode = itemNode;
         }
         synchronized (this) {
            wait(); //进入等待
         }
         return true;
      } catch (Exception e){
         e.printStackTrace();
      }
   }

   @Override
   public void close() throws Exception {  //自动释放锁
      zookeeper.delete(tempNode, -1);  //删除节点
      zookeeper.close();  //关闭
   }
   @Override
   public void process(WatchedEvent e) {
      if(e.getType() == Event.EventType.NodeDeleted){
         synchronized(this){
            notify(); //取消等待
         }
      }
   }
}

(3)使用ZooKeeper锁:

public void test(){
   try (ZooKeeperLock lock = new ZooKeeperLock()) {
      if (lock.getZKLock("业务名称")){  //获取锁,true表示有锁,false为没有锁
         //此处执行锁内具体逻辑
      }
   } catch (Exception e) {
      e.printStackTrace();   
   }
}

5.Curator分布式锁(依赖ZooKeeper):

(1)导入Curator依赖包,在pom.xml中:

<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>4.2.0</version>
</dependency>

(2)使用Curator锁:

Application类中:

//启动类中实例化CuratorFramework
@StringBootApplication
public class MyApplication{
   ...   //其他代码

   @Bean(initMethod = "start", destroyMethod = "close")  //初始化时调用CuratorFramework的start()方法,结束时调用CuratorFramework的close()方法
   public CuratorFramework getCuratorFramework(){ //实例化CuratorFramework
      RetryPolicy rp = new ExponentialBackoffRetry(1000, 3)
      return CuratorFrameworkFactory.newClient("192.168.233.141:2181", rp);  //连接ZooKeeper服务器IP与端口,返回CuratorFramework客户端对象
   }
}

在需要的地方使用锁:

@Autowired
CuratorFramework client;

public void test(){
   InterProcessMutex lock = new InterProcessMutex(client, "/name");  //name为业务名称
   try{
      if(lock.acquire(30, TimeUnit.SECONDS)){  //获取锁
         //此处执行锁内具体逻辑
      }
   } catch (Exception e){
      e.printStackTrace();
   } finally {
      try{
         lock.release();  //释放锁
      } catch (Exception e){
         e.printStackTrace();
      }
   }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值