java分布式锁-redis实现(一)

前言

最近由于项目需要,需要增加分布式锁功能,因此,对分布式锁进行了研究。

技术选型

分布式锁有三种实现方式,zookeeper、数据库、redis。由于redis效率高,使用简单,因此,在这里选用了redis实现分布式锁。

配置redis

由于项目是springboot项目,整合了RedisTemplate,直接在利用RedisTemplate实现分布式锁较为简单,无需增加其他配置,配置如下:


spring.redis.host=10.1.1.1
spring.redis.port=6379
spring.redis.password=123456

pom.xml文件如下:

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version> <!-- lookup parent from repository -->
    </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
      <!--spring boot 配置处理器 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

添加Lock服务

package com.tp.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

@Service
public class RedisLockService {
    @Autowired
    private RedisTemplate redisTemplate;

    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
    //锁超时时间,防止线程在入锁以后,无限的执行等待
    private int expireMsecs = 2 * 1000;
    //锁等待时间,防止线程饥饿
    private int timeoutMsecs = 5 * 1000;
    private volatile boolean locked = false;
    private String lockKey = "lock-key";


    //获取键的值
    private String get(final String key) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    byte[] data = connection.get(serializer.serialize(key));
                    connection.close();
                    if (data == null) {
                        return null;
                    }
                    return serializer.deserialize(data);
                }
            });
        } catch (Exception e) {
            System.err.println("get redis error, key : " + key + "," + e.getMessage());
        }
        return obj != null ? obj.toString() : null;
    }

    //设置键的值
    private boolean setNX(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    //System.out.println(key + ":" + value);
                    Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return success;
                }
            });
        } catch (Exception e) {
            System.err.println("setNX redis error, key : " + key + "," + e.getMessage());
            e.printStackTrace();
        }
        return obj != null ? (Boolean) obj : false;
    }

    //获取并设置键的值,并返回旧值
    private String getSet(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute(new RedisCallback<Object>() {
                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    StringRedisSerializer serializer = new StringRedisSerializer();
                    byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
                    connection.close();
                    return serializer.deserialize(ret);
                }
            });
        } catch (Exception e) {
            System.err.println("setNX redis error, key : " + key);
            e.printStackTrace();
        }
        return obj != null ? (String) obj : null;
    }

    //实现分布式锁
    public synchronized boolean lock(String lockValue) {
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            //long expires = System.currentTimeMillis() + expireMsecs + 1;
            //String expiresStr = String.valueOf(expires); //锁到期时间
            if (this.setNX(lockKey, lockValue)) {
                // 获取锁
                locked = true;
                return true;
            }
            //redis系统的时间
            String currentValueStr = this.get(lockKey);
            //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
            if (currentValueStr != null && Long.parseLong(currentValueStr) <= System.currentTimeMillis()) {

                //获取上一个锁到期时间,并设置现在的锁到期时间,
                //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
                String oldValueStr = this.getSet(lockKey, lockValue);

                //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    //分布式的情况下:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
                    // 获取锁
                    locked = true;
                    return true;
                }
            }
            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
            //延迟100 毫秒,
            try {
                Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
            }catch (Exception e){
                System.err.println(lockKey + e.getMessage());
            }
        }
        return false;
    }

    //释放锁
    public boolean releaseLock(String lockValue) {
        // 判断加锁与解锁是不是同一个客户端
        boolean flag = false;
        try{
            String value = get(lockKey);
            //System.out.println(lockValue + ":" + value);
            if (lockValue.equals(value)){
                redisTemplate.delete(lockKey);
                flag = true;
            }
        }catch (Exception e){
            System.err.println("删除锁出错" + e.getMessage());
        }
        return flag;
    }
}

使用demo

package com.tp.service;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class LockDemo {
    @Autowired
    private RedisLockService redisLockService;

    @PostConstruct
    public void lockDemo(){
        int i = 100;
        while (i > 0){
            i--;
            new Thread(){
                @Override
                public void run(){
                    String lockKey = String.valueOf(Thread.currentThread().getId());
                    String lockValue = String.valueOf(System.currentTimeMillis() + 2000 + 1);
                    Boolean result = redisLockService.lock(lockValue);
                    if (result){
                        System.out.println(lockKey + ":获取锁");
                    }else{
                        System.out.println(lockKey + ":未能获取锁");
                    }
                    try{
                        Thread.sleep(10);
                    }catch (Exception e){
                        System.err.println(lockKey + e.getMessage());
                    }
                    boolean release = redisLockService.releaseLock(lockValue);
                    if (release){
                        System.out.println(lockKey + ":释放锁成功");
                    }else {
                        System.out.println(lockKey + ":未释放锁");
                    }
                }
            }.start();
        }
    }

}

结果

在这里插入图片描述

总结

使用redis实现分布式锁的方式有很多种,有的使用jedis的setex方法,直接设置过期时间,因为这个方法是原子性的,避免线程死锁发生。这里呢,使用的是redis的setnx,该方法当key不存在时就不能set进去,使用了其他逻辑来避免发生死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值